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