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