MOD légères modifs
[euphorik.git] / modules / erl / euphorik_bd.erl
1 % coding: utf-8
2 % Copyright 2008 Grégory Burri
3 %
4 % This file is part of Euphorik.
5 %
6 % Euphorik is free software: you can redistribute it and/or modify
7 % it under the terms of the GNU General Public License as published by
8 % the Free Software Foundation, either version 3 of the License, or
9 % (at your option) any later version.
10 %
11 % Euphorik is distributed in the hope that it will be useful,
12 % but WITHOUT ANY WARRANTY; without even the implied warranty of
13 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 % GNU General Public License for more details.
15 %
16 % You should have received a copy of the GNU General Public License
17 % along with Euphorik. If not, see <http://www.gnu.org/licenses/>.
18 %
19 % Ce module permet de gérer les données persistantes lié au site d'euphorik.ch.
20 % Il permet d'ajouter des message, de demande les messages sur une page donnée, etc..
21 % Ce module utilise une base mnesia.
22 % @author G.Burri
23
24
25 -module(euphorik_bd).
26 -export([
27 % gestion :
28 create/0,
29 connect/0,
30 connect/1,
31 reset/0,
32 update/0,
33
34 % users :
35 nouveau_user/2,
36 nouveau_user/3,
37 set_profile/11,
38 update_date_derniere_connexion/1,
39 update_ip/2,
40 update_pseudo_user/2,
41 print_users/0,
42 print_users/1,
43 print_user/1,
44 user_by_cookie/1,
45 user_by_id/1,
46 user_by_login/1,
47 user_by_login_password/2,
48 user_by_mess/1,
49 toggle_ek_master/1,
50 css_from_user_cookie/1,
51
52 % messages :
53 nouveau_message/3,
54 nouveau_message_sys/1,
55 nouveau_message_sys/2,
56 messages/1,
57 messages/2,
58 messages/3,
59 message_by_id/1,
60 messages_by_ids/1,
61 message_existe/1,
62 reponses/0,
63 repond_a/1,
64 est_une_reponse_a_user/2,
65 a_repondu_a_message/2,
66 possede_message/2,
67
68 % ip :
69 list_ban/0,
70 ban/2,
71 deban/1,
72 est_banni/1,
73 can_register/1,
74
75 % trolls :
76 trolls/0,
77 trolls/1,
78 put_troll/2,
79 mod_troll/2,
80 del_troll/1,
81 troll_by_id/1,
82 current_troll/0,
83 elire_troll/0,
84 message_id_associe/1,
85
86 % versions :
87 update_version/1,
88
89 % utiles :
90 resultat_transaction/1
91 ]).
92 -import(qlc, [e/1, e/2, q/1, cursor/1]).
93 -include("../include/euphorik_bd.hrl").
94 -include("../include/euphorik_defines.hrl").
95 -include_lib("stdlib/include/qlc.hrl").
96
97
98 % Instructions pour créer une nouvelle base :
99 % $erl -sname yaws -mnesia dir '"/projets/euphorik/BD"'
100 % voir doc/installation.txt
101 % >l(euphorik_bd).
102 % >euphorik_bd:create().
103 create() ->
104 mnesia:stop(),
105 mnesia:delete_schema([node()]),
106 mnesia:create_schema([node()]), % nécessaire pour les tables sur disc
107 mnesia:start(),
108 create_tables(),
109 reset().
110 create_tables() ->
111 mnesia:create_table(counter, [
112 {attributes, record_info(fields, counter)},
113 {disc_copies, [node()]}
114 ]),
115 mnesia:create_table(proprietes, [
116 {attributes, record_info(fields, proprietes)},
117 {disc_copies, [node()]}
118 ]),
119 mnesia:create_table(minichat, [
120 {attributes, record_info(fields, minichat)},
121 {index, [auteur_id, troll_id]},
122 {disc_copies, [node()]}
123 ]),
124 mnesia:create_table(reponse_minichat, [
125 {type, bag},
126 {attributes, record_info(fields, reponse_minichat)},
127 {index, [cible]},
128 {disc_copies, [node()]}
129 ]),
130 mnesia:create_table(user, [
131 {attributes, record_info(fields, user)},
132 {index, [cookie, login]},
133 {disc_copies, [node()]}
134 ]),
135 mnesia:create_table(ip_table, [
136 {attributes, record_info(fields, ip_table)},
137 {disc_copies, [node()]}
138 ]),
139 mnesia:create_table(troll, [
140 {attributes, record_info(fields, troll)},
141 {index, [date_post]},
142 {disc_copies, [node()]}
143 ]).
144
145
146 % Connexion à la base de données de yaws sur overnux
147 connect() ->
148 connect(yaws@flynux).
149 connect(Node) ->
150 mnesia:start(),
151 mnesia:change_config(extra_db_nodes, [Node]).
152
153
154 % Efface tous les users, minichat_reponse et minichat.
155 reset() ->
156 mnesia:clear_table(counter),
157 mnesia:clear_table(user),
158 mnesia:clear_table(reponse_minichat),
159 mnesia:clear_table(minichat),
160 mnesia:clear_table(troll),
161 mnesia:clear_table(ip_table),
162 % crée l'utilisateur root
163 mnesia:transaction(fun() ->
164 mnesia:write(#proprietes{nom = version, valeur = ?VERSION_BD}),
165 User = #user{id = 0, pseudo = "Sys", login = "Sys", date_creation = now(), date_derniere_connexion = now(), ek_master = true},
166 mnesia:write(User),
167 User
168 end).
169
170
171 % Met à jour la bd, compare ?VERSION_BD avec la version dans la table 'proprietes'
172 % et exécute les patchs nécessaires.
173 update() ->
174 mnesia:transaction(
175 fun() ->
176 case mnesia:read({proprietes, version}) of
177 [#proprietes{nom = Version}] ->
178 update(Version);
179 _ ->
180 erreur
181 end
182 end
183 ).
184
185
186 % Mise à jour de la BD.
187 % attention : il est nécessaire de se trouver dans une transaction.
188 update(?VERSION_BD) -> fini;
189 update(Version) ->
190 patch(Version),
191 update(Version + 1).
192
193
194 % Applique une modification de la BD pour passer d'une version à la suivante.
195 % 1 -> 2
196 patch(1) ->
197 ok.
198 % 2 -> 3
199 %patch(2) ->
200
201
202 % Ajoute un nouveau user et le renvoie
203 nouveau_user(Pseudo, Cookie) ->
204 F = fun() ->
205 Id = nouvel_id(user),
206 User = #user{id = Id, cookie = Cookie, pseudo = Pseudo, date_creation = now(), date_derniere_connexion = now()},
207 mnesia:write(User),
208 User
209 end,
210 resultat_transaction(mnesia:transaction(F)).
211
212
213 % Ajoute un nouveau user et le renvoie
214 nouveau_user(Login, Password, Cookie) ->
215 F = fun() ->
216 Id = nouvel_id(user),
217 User = #user{id = Id, cookie = Cookie, pseudo = Login, login = Login, password = Password, date_creation = now(), date_derniere_connexion = now()},
218 mnesia:write(User),
219 User
220 end,
221 resultat_transaction(mnesia:transaction(F)).
222
223
224 % Mise à par Cookie les autres peuvent être undefined ce qui veut dire qu'ils ne seront pas modifié.
225 set_profile(Cookie, Login, Password, Pseudo, Email, Css, Nick_format, View_times, View_tooltips, Page_principale, Conversations) ->
226 if Nick_format =:= nick; Nick_format =:= login; Nick_format =:= nick_login ->
227 resultat_transaction(mnesia:transaction(
228 fun() ->
229 case user_by_cookie(Cookie) of
230 {ok, User} ->
231 case user_by_login(Login) of
232 {ok, U} when Login =/= [], U#user.id =/= User#user.id ->
233 login_deja_pris;
234 _ ->
235 User_modifie = User#user{
236 % TODO : pourquoi ne pas tester avec la valeur "undefined" plutôt qu'avec "is_list" ?
237 % TODO : validation plus strict des données (pas de page négative dans les conv par exemple)
238 login = if is_list(Login) -> Login; true -> User#user.login end,
239 password = if is_list(Password) andalso Password =/= [] -> Password; true -> User#user.password end,
240 pseudo = if is_list(Pseudo) -> Pseudo; true -> User#user.pseudo end,
241 email = if is_list(Email) -> Email; true -> User#user.email end,
242 css = if is_list(Css) -> Css; true -> User#user.css end,
243 nick_format = Nick_format,
244 view_times = View_times,
245 view_tooltips = View_tooltips,
246 page_principale = if is_integer(Page_principale), Page_principale > 0 -> Page_principale; true -> User#user.page_principale end,
247 conversations = if is_list(Conversations) -> Conversations; true -> User#user.conversations end
248 },
249 mnesia:write(User_modifie),
250 ok
251 end;
252 _ -> erreur
253 end
254 end
255 ));
256 true ->
257 erreur
258 end.
259
260
261 % Met à jour la date de la dernière connexion d'un utilisateur à maintenant
262 update_date_derniere_connexion(User_id) ->
263 mnesia:transaction(
264 fun() ->
265 case mnesia:wread({user, User_id}) of
266 [User] ->
267 mnesia:write(User#user{date_derniere_connexion = now()});
268 _ ->
269 mnesia:abort("update_date_derniere_connexion: User inconnu")
270 end
271 end
272 ).
273
274
275 % Met à jour l'ip d'un user
276 update_ip(User_id, IP) ->
277 mnesia:transaction(
278 fun() ->
279 case mnesia:wread({user, User_id}) of
280 [User] ->
281 mnesia:write(User#user{last_ip = IP});
282 _ ->
283 mnesia:abort("update_ip: User inconnu")
284 end
285 end
286 ).
287
288
289 % Met à jour le pseudo du user
290 update_pseudo_user(UserId, Pseudo) ->
291 mnesia:transaction(
292 fun() ->
293 case mnesia:wread({user, UserId}) of
294 [User] when User#user.pseudo =/= Pseudo ->
295 mnesia:write(User#user{pseudo = Pseudo});
296 _ ->
297 mnesia:abort("update_pseudo_user: User inconnu ou pseudo deja à jour")
298 end
299 end
300 ).
301
302
303 % Affiche N user trié par leur date de dernière connexion.
304 % Attention : pas d'index sur ce champs (la date)
305 print_users(N) ->
306 resultat_transaction(mnesia:transaction(fun() ->
307 C = cursor(q([E || E <- qlc:keysort(9, mnesia:table(user), [{order, descending}])])),
308 Users = qlc:next_answers(C, N),
309 lists:foreach(
310 fun(U) ->
311 print_user(U)
312 end,
313 Users
314 ),
315 qlc:delete_cursor(C)
316 end)).
317
318
319 % Affiche tous les users.
320 print_users() ->
321 print_users(all_remaining).
322
323
324 print_user(User) when is_record(User, user) ->
325 #user{id = Id, pseudo = Pseudo, login = Login, ek_master = Ek_master, date_derniere_connexion = Date} = User,
326 {{Annee, Mois, Jour}, {Heure, Min, _}} = calendar:now_to_local_time(Date),
327 io:format(
328 "~4w : ~10.10..s~10.10..s ~2w.~2.2.0w.~w - ~2wh~2.2.0w~n",
329 [
330 Id,
331 if Ek_master -> "*"; true -> "" end ++ Pseudo,
332 "(" ++ Login ++ ")",
333 Jour, Mois, Annee, Heure, Min
334 ]
335 );
336 % Affichage d'un user en fonction de son login
337 print_user(Login) when is_list(Login) ->
338 case user_by_login(Login) of
339 {ok, User} ->
340 print_user(User);
341 _ ->
342 {erreur, "Login pas trouvé : " ++ Login}
343 end;
344 % Affichage d'un user en fonction de son id
345 print_user(Id) when is_integer(Id) ->
346 case user_by_id(Id) of
347 {ok, User} ->
348 print_user(User);
349 _ ->
350 {erreur, "Id pas trouvé : " ++ integer_to_list(Id)}
351 end.
352
353
354 % Est-ce qu'un utilisateur existe en fonction de son cookie ?
355 % Renvoie {ok, User} ou erreur
356 user_by_cookie(Cookie) ->
357 resultat_transaction(mnesia:transaction(
358 fun() ->
359 case e(q([E || E <- mnesia:table(user), E#user.cookie =:= Cookie])) of
360 [User] -> {ok, User};
361 _ -> erreur
362 end
363 end
364 )).
365
366
367 user_by_id(ID) ->
368 resultat_transaction(mnesia:transaction(
369 fun() ->
370 case e(q([E || E <- mnesia:table(user), E#user.id =:= ID])) of
371 [User] -> {ok, User};
372 _ -> erreur
373 end
374 end
375 )).
376
377
378 user_by_login(Login) ->
379 resultat_transaction(mnesia:transaction(
380 fun() ->
381 Users = e(q([E || E <- mnesia:table(user), E#user.login =:= Login])),
382 case Users of
383 [User] -> {ok, User};
384 _ -> erreur
385 end
386 end
387 )).
388
389
390 toggle_ek_master(User_id) ->
391 resultat_transaction(mnesia:transaction(
392 fun() ->
393 Users = e(q([E || E <- mnesia:table(user), E#user.id =:= User_id])),
394 case Users of
395 [User] ->
396 mnesia:write(User#user{ek_master = not User#user.ek_master});
397 _ -> erreur
398 end
399 end
400 )).
401
402
403 % Renvoie une chaine représentant le cookie ou undefined si pas trouvé.
404 css_from_user_cookie(Cookie) ->
405 case user_by_cookie(Cookie) of
406 {ok, User} ->
407 User#user.css;
408 _ ->
409 undefined
410 end.
411
412
413
414
415 user_by_login_password(Login, Password) ->
416 resultat_transaction(mnesia:transaction(
417 fun() ->
418 case e(q([E || E <- mnesia:table(user), E#user.login =:= Login, E#user.password =:= Password])) of
419 [User | _] -> {ok, User};
420 _ -> erreur
421 end
422 end
423 )).
424
425
426 % Renvoie {ok, User} où User est un #user possédant le message donné.
427 user_by_mess(Id) ->
428 resultat_transaction(mnesia:transaction(
429 fun() ->
430 case e(q([U || U <- mnesia:table(user), M <- mnesia:table(minichat), M#minichat.id =:= Id, M#minichat.auteur_id =:= U#user.id])) of
431 [User | _] -> {ok, User};
432 _ -> erreur
433 end
434 end
435 )).
436
437
438 % Ajoute un message. Repond_A est une liste d'id auquel le message répond
439 % retourne soit l'id du message soit {erreur, <raison>}.
440 nouveau_message(Mess, Auteur_id, Repond_A) ->
441 % regarde si les id 'Repond_A' existent
442 F = fun() ->
443 Nb_id_trouve = length(e(q([E#minichat.id || E <- mnesia:table(minichat), lists:member(E#minichat.id, Repond_A)]))),
444 % est-ce que l'auteur existe ?
445 Auteur = case e(q([E || E <- mnesia:table(user), E#user.id =:= Auteur_id])) of
446 [A] -> A;
447 _ -> {erreur, "L'auteur du message est introuvable"}
448 end,
449 if Nb_id_trouve =/= length(Repond_A) ->
450 {erreur, "Un ou plusieurs messages introuvable"};
451 true ->
452 % comparaison entre la date du dernier poste et maintenant (gestion du flood)
453 Delta = delta_date_ms(Auteur#user.date_derniere_connexion, now()),
454 Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end,
455 Auteur_maj = Auteur#user{
456 indice_flood = if Nouvel_indice_flood > ?INDICE_SPAM_MAX -> ?INDICE_SPAM_MAX; Nouvel_indice_flood < 0 -> 0; true -> Nouvel_indice_flood end,
457 date_derniere_connexion = now()
458 },
459 % est-ce que l'auteur à trop floodé ?
460 if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
461 mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}),
462 nouveau_message_sys("''" ++ Auteur#user.pseudo ++ if Auteur#user.login =/= [] -> " (" ++ Auteur#user.login ++ ")"; true -> "" end ++ "'' est bloqué pour " ++ integer_to_list(trunc(?DUREE_BLOCAGE_SPAM / 1000)) ++ " secondes pour cause de flood.");
463 Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
464 {erreur, "Bloqué pour cause de flood"};
465 true ->
466 mnesia:write(Auteur_maj),
467 Id = nouvel_id(minichat),
468 inserer_reponses(Id, Repond_A),
469 mnesia:write(#minichat{id=Id, auteur_id=Auteur#user.id, date=now(), pseudo=Auteur#user.pseudo, contenu=Mess}),
470 Id
471 end
472 end
473 end,
474 resultat_transaction(mnesia:transaction(F)).
475
476 % Définit Id_repondant comme étant la réponse à Ids. Ids est une liste d'id.
477 inserer_reponses(Id_repondant, [Id_mess | Reste]) ->
478 mnesia:write(#reponse_minichat{repondant = Id_repondant, cible = Id_mess}),
479 inserer_reponses(Id_repondant, Reste);
480 inserer_reponses(_, []) ->
481 ok.
482
483
484 % Permet de créer un message système.
485 % Renvoie l'id du message système
486 nouveau_message_sys(Mess) ->
487 nouveau_message_sys(Mess, undefined).
488
489
490 % Création d'un message système lié à un troll.
491 nouveau_message_sys(Mess, Troll_id) ->
492 {ok, Root} = user_by_id(0),
493 resultat_transaction(mnesia:transaction(
494 fun() ->
495 Id = nouvel_id(minichat),
496 mnesia:write(#minichat{id=Id, auteur_id=0, date=now(), pseudo=Root#user.pseudo, contenu=Mess, troll_id=Troll_id}),
497 Id
498 end
499 )).
500
501
502 % Renvoie N messages se trouvant sur la première page
503 messages(N) ->
504 messages(N, 1).
505
506
507 % Renvoie N messages se trouvant sur la page P
508 messages(N, P) ->
509 F = fun() ->
510 C = cursor(q([
511 E#minichat{contenu = contenu_message(E)} ||
512 E <- qlc:keysort(2, mnesia:table(minichat), [{order, descending}])
513 ])),
514 if P > 1 -> qlc:next_answers(C, N * (P - 1));
515 true -> ok
516 end,
517 R = qlc:next_answers(C, N),
518 qlc:delete_cursor(C),
519 lists:reverse(R)
520 end,
521 resultat_transaction(mnesia:transaction(F)).
522
523
524 % Renvoie les messages manquants pour la page P en sachant qu'il y a N message
525 % par page et que le dernier message que l'on possède est Id
526 messages(Id, N, P) ->
527 lists:filter(fun (M) -> M#minichat.id > Id end, messages(N, P)).
528
529
530 % Renvoie {ok, #minichat} (voir #minichat de euphorik_bd.hrl) à partir de son id.
531 message_by_id(Id) ->
532 case resultat_transaction(mnesia:transaction(
533 fun() ->
534 e(q([E#minichat{contenu = contenu_message(E)} || E <- qlc:keysort(2, mnesia:table(minichat), [{order, ascending}]), Id =:= E#minichat.id]))
535 end
536 )) of
537 [M] -> {ok, M};
538 _ -> erreur
539 end.
540
541
542 % Renvoie le contenu d'un message donnée, à utiliser à l'intérieur d'une transaction.
543 % TODO : Cette fonction pourrait être remplacé par un "outer-join", est-ce possible avec qlc ?
544 contenu_message(E) ->
545 lists:flatten(E#minichat.contenu ++ e(q([T#troll.content || T <- mnesia:table(troll), T#troll.id =:= E#minichat.troll_id]))).
546
547
548 % Renvoie une liste de message (voir #minichat de euphorik_bd.hrl) à partir d'une liste d'id (Ids).
549 messages_by_ids(Ids) ->
550 resultat_transaction(mnesia:transaction(
551 fun() ->
552 % TODO : optimisations ? serait-ce du O(n) ?
553 Query = q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, ascending}]), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]),
554 e(Query)
555 end
556 )).
557
558
559 % Est-ce qu'un message existe ? Renvoie un boolean.
560 % TODO : ya pas plus simple ?
561 message_existe(Id) ->
562 resultat_transaction(mnesia:transaction(fun() ->
563 length(e(q([E#minichat.id || E <- mnesia:table(minichat), E#minichat.id =:= Id]))) =:= 1
564 end)).
565
566
567 % Renvoie les reponses (utilisé normalement uniquement pendant le debug).
568 reponses() ->
569 F = fun() ->
570 e(q([E || E <- mnesia:table(reponse_minichat)]))
571 end,
572 resultat_transaction(mnesia:transaction(F)).
573
574
575 % Renvoie les messages auquel M_id répond.
576 repond_a(M_id) ->
577 resultat_transaction(mnesia:transaction(
578 fun() ->
579 e(q(
580 [M || E <- mnesia:table(reponse_minichat),
581 M <- mnesia:table(minichat),
582 E#reponse_minichat.repondant =:= M_id,
583 M#minichat.id =:= E#reponse_minichat.cible]))
584 end
585 )).
586
587
588 % Est-ce que le message Id_mess est une réponse d'une message de Id_user ?
589 est_une_reponse_a_user(Id_user, Id_mess) ->
590 case mnesia:transaction(
591 fun() ->
592 e(q([
593 M#minichat.auteur_id || M <- mnesia:table(minichat), R <- mnesia:table(reponse_minichat),
594 M#minichat.auteur_id =:= Id_user, M#minichat.id =:= R#reponse_minichat.cible, R#reponse_minichat.repondant =:= Id_mess
595 ]), [{unique_all, true}])
596 end
597 ) of
598 {atomic, [_]} -> true;
599 _ -> false
600 end.
601
602
603 % Est-ce que Id_user à répondu au message Id_mess
604 a_repondu_a_message(Id_user, Id_mess) ->
605 case mnesia:transaction(
606 fun() ->
607 e(q([
608 M#minichat.auteur_id || M <- mnesia:table(minichat), R <- mnesia:table(reponse_minichat),
609 R#reponse_minichat.cible =:= Id_mess, R#reponse_minichat.repondant =:= M#minichat.id, M#minichat.auteur_id =:= Id_user
610 ]), [{unique_all, true}])
611 end
612 ) of
613 {atomic, [_]} -> true;
614 _ -> false
615 end.
616
617
618 % Est-ce que Id_user possède Id_mess ?
619 possede_message(Id_user, Id_mess) ->
620 case mnesia:transaction(
621 fun() ->
622 e(q([E#minichat.auteur_id || E <- mnesia:table(minichat), E#minichat.id =:= Id_mess]))
623 end
624 ) of
625 {atomic, [Id_user | []]} -> true;
626 _ -> false
627 end.
628
629
630 % renvoie la liste des ip bannies
631 % liste de {ip, temps_restant(en minutes), Users} ou Users est une liste de {pseudo, login}
632 % TODO : déterminer la complexité de cette fonction. (Il n'y a pas d'index sur #user.last_ip)
633 list_ban() ->
634 resultat_transaction(mnesia:transaction(
635 fun() ->
636 Now = now(),
637 e(q([
638 {
639 IP#ip_table.ip,
640 delta_date_minute(date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration), Now),
641 e(q([{U#user.pseudo, U#user.login} || U <- mnesia:table(user), U#user.last_ip =:= IP#ip_table.ip]))
642 } ||
643 IP <- qlc:keysort(2, mnesia:table(ip_table)),
644 if IP#ip_table.ban =:= undefined -> false; true -> date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration) > Now end
645 ]))
646 end
647 )).
648
649
650 % Bannie une ip pour un certain temps (en minute).
651 ban(IP, Duration) ->
652 mnesia:transaction(
653 fun() ->
654 case mnesia:wread({ip_table, IP}) of
655 [IP_tuple] ->
656 mnesia:write(IP_tuple#ip_table{ban = now(), ban_duration = Duration});
657 _ ->
658 mnesia:write(#ip_table{ip = IP, ban = now(), ban_duration = Duration})
659 end
660 end
661 ).
662
663
664 % Débanni une ip
665 deban(IP) ->
666 ban(IP, 0).
667
668
669 % Renvoie soit {true, Temps} où Temps est le temps en minutes pendant lequel le user est encore banni
670 % ou false.
671 est_banni(User_id) ->
672 resultat_transaction(mnesia:transaction(
673 fun() ->
674 case e(q([
675 {IP#ip_table.ban, IP#ip_table.ban_duration} ||
676 U <- mnesia:table(user),
677 U#user.id =:= User_id,
678 IP <- mnesia:table(ip_table),
679 IP#ip_table.ip =:= U#user.last_ip
680 ])) of
681 [{Ban, Ban_duration}] ->
682 Echeance = date_plus_minutes(Ban, Ban_duration),
683 Now = now(),
684 if Echeance < Now -> % l'échéance est passée
685 false;
686 true ->
687 {true, delta_date_minute(Echeance, Now)}
688 end;
689 _ ->
690 false
691 end
692 end
693 )).
694
695
696 % Ban est une date tel que retourner par now().
697 % Ban_duration est un temps en minutes.
698 % retourne une date.
699 date_plus_minutes(Ban, Ban_duration) ->
700 Duration_sec = Ban_duration * 60,
701 {MegaSec, Sec, MicroSec} = Ban,
702 {MegaSec + if Sec + Duration_sec >= 1000000 -> 1; true -> 0 end,(Sec + Duration_sec) rem 1000000, MicroSec}.
703
704
705 % Si deux enregistrements consequtifs de la même IP sont fait en moins d'une seconde alors
706 % ip_table.nb_try_register est incrémenté de 1 sinon il est décrémenté de 1 (jusqu'a 0).
707 % Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure.
708 can_register(IP) ->
709 resultat_transaction(mnesia:transaction(
710 fun() ->
711 case e(q([I || I <- mnesia:table(ip_table), I#ip_table.ip =:= IP])) of
712 [] ->
713 mnesia:write(#ip_table{ip = IP, date_last_try_register = now()}),
714 true;
715 [T] ->
716 Delta = delta_date_ms(T#ip_table.date_last_try_register, now()),
717 if T#ip_table.nb_try_register =:= ?NB_MAX_FLOOD_REGISTER, Delta < ?TEMPS_BAN_FLOOD_REGISTER ->
718 false;
719 true ->
720 mnesia:write(#ip_table{
721 ip = IP,
722 date_last_try_register = now(),
723 nb_try_register = T#ip_table.nb_try_register + if Delta < ?TEMPS_FLOOD_REGISTER -> 1; T#ip_table.nb_try_register > 0 -> -1; true -> 0 end
724 }),
725 true
726 end
727 end
728 end
729 )).
730
731
732 % Renvoie tous les trolls
733 trolls() ->
734 resultat_transaction(mnesia:transaction(
735 fun() ->
736 e(q([T || T <- qlc:keysort(2, mnesia:table(troll))]))
737 end
738 )).
739
740
741 % Renvoie les trolls manquants posté après Last_id.
742 trolls(Last_id) ->
743 resultat_transaction(mnesia:transaction(
744 fun() ->
745 e(q([T || T <- qlc:keysort(2, mnesia:table(troll)), T#troll.id > Last_id, T#troll.date_post =:= undefined]))
746 end
747 )).
748
749
750 % Crée un nouveau troll.
751 % Renvoie l'id du nouveau troll
752 % ou max_troll_reached_per_user si le nombre de troll posté par l'utilisateur max a été atteind
753 % ou max_troll_reached si le nombre de troll posté max a été atteind
754 % ou user_unknown
755 put_troll(User_id, Content) ->
756 resultat_transaction(mnesia:transaction(
757 fun() ->
758 % control le nombre de troll déjà posté
759 Nb_troll_poste_par_user = length(e(q(
760 [
761 E#troll.id || E <- mnesia:table(troll),
762 E#troll.id_user =:= User_id,
763 E#troll.date_post =:= undefined
764 ]
765 ))),
766 Nb_troll_poste_total = length(e(q(
767 [
768 E#troll.id || E <- mnesia:table(troll),
769 E#troll.date_post =:= undefined
770 ]
771 ))),
772 User = user_by_id(User_id),
773 case User of
774 {ok, _} ->
775 if Nb_troll_poste_par_user >= ?NB_MAX_TROLL_WAITING_BY_USER ->
776 max_troll_reached_per_user;
777 Nb_troll_poste_total >= ?NB_MAX_TROLL_WAITING ->
778 max_troll_reached;
779 true ->
780 Id = nouvel_id(minichat),
781 mnesia:write(#troll{id = Id, id_user = User_id, date_create = now(), content = Content}),
782 Id
783 end;
784 _ ->
785 user_unknown
786 end
787 end
788 )).
789
790
791 % renvoie ok | erreur
792 mod_troll(Troll_id, Content) ->
793 mnesia:transaction(
794 fun() ->
795 case mnesia:wread({troll, Troll_id}) of
796 [Troll = #troll{date_post = undefined}] ->
797 mnesia:write(Troll#troll{content = Content});
798 _ ->
799 mnesia:abort("mod_troll: Troll inconnu ou déjà posté")
800 end
801 end
802 ).
803
804
805 del_troll(Troll_id) ->
806 mnesia:transaction(
807 fun() ->
808 case mnesia:wread({troll, Troll_id}) of
809 [#troll{date_post = undefined}] ->
810 mnesia:delete({troll, Troll_id});
811 _ ->
812 mnesia:abort("mod_troll: Troll inconnu ou déjà posté")
813 end
814 end
815 ).
816
817
818 troll_by_id(Troll_id) ->
819 resultat_transaction(mnesia:transaction(
820 fun() ->
821 case e(q([T || T <- mnesia:table(troll), T#troll.id =:= Troll_id])) of
822 [T] -> {ok, T};
823 _ ->
824 erreur
825 end
826 end
827 )).
828
829
830 % Renvoie le troll actuel qui se trouve sur la page principale.
831 % Renvois aucun si pas de troll courant.
832 current_troll() ->
833 resultat_transaction(mnesia:transaction(
834 fun() ->
835 % TODO : ya pas moyen de désigner le champs plutot qu'avec un nombre pour le tri ?
836 C = cursor(q([T || T <- qlc:keysort(5, mnesia:table(troll), [{order, descending}]), T#troll.date_post =/= undefined])),
837 R = case qlc:next_answers(C, 1) of
838 [T] -> T;
839 _ -> aucun
840 end,
841 qlc:delete_cursor(C),
842 R
843 end
844 )).
845
846
847 % Elit un troll au hasard parmis les trolls en attente (leur date_post =:= undefined)
848 % Un message est posté par 'Sys' et le troll elu est lié à ce message
849 % met à jour sa date de post.
850 % renvoie plus_de_trolls si il n'y a aucun troll en attente.
851 elire_troll() ->
852 {A1,A2,A3} = now(),
853 random:seed(A1, A2, A3),
854 mnesia:transaction(
855 fun() ->
856 case e(q([T || T <- mnesia:table(troll), T#troll.date_post =:= undefined])) of
857 [] ->
858 plus_de_trolls;
859 Trolls ->
860 Troll = lists:nth(random:uniform(length(Trolls)), Trolls),
861 Troll2 = Troll#troll{date_post = now()},
862 mnesia:write(Troll2),
863 nouveau_message_sys("Troll de la semaine : ", Troll2#troll.id)
864 end
865 end
866 ).
867
868
869 % Renvoie l'id du message associé au troll dont l'id est donnée.
870 % Renvoie undefined si il n'y en a pas.
871 message_id_associe(Troll_id) ->
872 resultat_transaction(mnesia:transaction(
873 fun() ->
874 case e(q([M#minichat.id || M <- mnesia:table(minichat), M#minichat.troll_id =:= Troll_id])) of
875 [Id] -> Id;
876 _ -> undefined
877 end
878 end
879 )).
880
881
882 update_version(1) ->
883 mnesia:transform_table(
884 ip_table,
885 fun() -> null end,
886 record_info(fields, ip_table),
887 ip_table
888 ).
889
890
891 % Renvoie le résultat d'une transaction (en décomposant le tuple fournit)
892 resultat_transaction({_, T}) ->
893 T.
894
895
896 % Retourne la difference entre deux timestamp (erlang:now()) en miliseconde
897 delta_date_ms(D1, D2) ->
898 1000000000 * abs(element(1, D1) - element(1, D2)) + 1000 * abs(element(2, D1) - element(2, D2)) + trunc(abs(element(3, D1) - element(3, D2)) / 1000).
899
900 % Retourne la différence entre deux timestamp (erlang:now) en minutes
901 delta_date_minute(D1, D2) ->
902 trunc(delta_date_ms(D1, D2) / 1000 / 60).
903
904
905 % Bizarre, cette fonction n'existe pas dans la stdlib.
906 % Pas utilisé mais bon ca me fait de la peine de l'enlever.
907 ceiling(X) ->
908 T = trunc(X),
909 case (X - T) of
910 Neg when Neg < 0 -> T;
911 Pos when Pos > 0 -> T + 1;
912 _ -> T
913 end.
914
915
916 % Renvoie un nouvel id pour une table donnée
917 nouvel_id(Table) ->
918 mnesia:dirty_update_counter(counter, Table, 1).
919