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