bef8b1f3a754050bdd6f48a71ac094149805f4db
[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 = e(
281 q([M || M <- mnesia:table(minichat), lists:member(M#minichat.id, Repond_A_ids)]),
282 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
283 ),
284 Racine_id = case Repond_a of
285 [] -> undefined;
286 [M | _] ->
287 Une_racine = M#minichat.racine_id,
288 % vérification que tout les messages de Repond_a possède la même racine (même conversation)
289 case lists:all(fun(R) -> R#minichat.racine_id =:= Une_racine end, Repond_a) of
290 true ->
291 Une_racine;
292 _ ->
293 {erreur, "Les messages ne font pas partie de la même conversation"}
294 end
295 end,
296 case Racine_id of
297 {erreur, E} -> {erreur, E};
298 _ ->
299 % est-ce que l'auteur existe ?
300 case e(q([E || E <- mnesia:table(user), E#user.id =:= Auteur_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
301 [#user{profile = Profile} = Auteur] ->
302 if length(Repond_a) =/= length(Repond_A_ids) ->
303 {erreur, "Un ou plusieurs messages introuvable"};
304 true ->
305 % comparaison entre la date du dernier poste et maintenant (gestion du flood)
306 Delta = euphorik_common:delta_date_ms(Auteur#user.date_derniere_connexion, now()),
307 Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end,
308 Auteur_maj = Auteur#user{
309 indice_flood = if Nouvel_indice_flood > ?INDICE_SPAM_MAX -> ?INDICE_SPAM_MAX; Nouvel_indice_flood < 0 -> 0; true -> Nouvel_indice_flood end,
310 date_derniere_connexion = now()
311 },
312 % est-ce que l'auteur à trop floodé ?
313 if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
314 mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}),
315 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.");
316 Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
317 {erreur, "Bloqué pour cause de flood"};
318 true ->
319 mnesia:write(Auteur_maj),
320 Id = nouvel_id(minichat),
321 inserer_reponses(Id, Repond_A_ids),
322 mnesia:write(#minichat{
323 id = Id,
324 auteur_id = Auteur#user.id,
325 date = now(),
326 pseudo = Profile#profile.pseudo,
327 contenu = Mess,
328 racine_id = if Racine_id =:= undefined -> Id; true -> Racine_id end
329 }),
330 Id
331 end
332 end;
333 _ ->
334 {erreur, "L'auteur du message est introuvable"}
335 end
336 end
337 end,
338 resultat_transaction(mnesia:transaction(F)).
339
340 % Définit Id_repondant comme étant la réponse à Ids. Ids est une liste d'id.
341 inserer_reponses(Id_repondant, [Id_mess | Reste]) ->
342 mnesia:write(#reponse_minichat{repondant = Id_repondant, cible = Id_mess}),
343 inserer_reponses(Id_repondant, Reste);
344 inserer_reponses(_, []) ->
345 ok.
346
347
348 % Permet de créer un message système.
349 % Renvoie l'id du message système
350 nouveau_message_sys(Mess) ->
351 nouveau_message_sys(Mess, undefined).
352
353
354 % Création d'un message système lié à un troll.
355 nouveau_message_sys(Mess, Troll_id) ->
356 {ok, #user{profile = Profile}} = user_by_id(0),
357 resultat_transaction(mnesia:transaction(
358 fun() ->
359 Id = nouvel_id(minichat),
360 mnesia:write(#minichat{id = Id, auteur_id = 0, date = now(), pseudo = Profile#profile.pseudo, contenu = Mess, troll_id = Troll_id, racine_id = Id}),
361 Id
362 end
363 )).
364
365
366 % Renvoie N messages se trouvant sur la première page
367 messages(N) ->
368 messages(N, 1).
369
370
371 % Renvoie N messages se trouvant sur la page P
372 messages(N, P) ->
373 F = fun() ->
374 C = cursor(
375 qlc:keysort(
376 #minichat.id,
377 q([E#minichat{contenu = contenu_message(E)} || E <- mnesia:table(minichat)]),
378 [{order, descending}]
379 ),
380 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
381 ),
382 if P > 1 -> qlc:next_answers(C, N * (P - 1));
383 true -> ok
384 end,
385 R = qlc:next_answers(C, N),
386 qlc:delete_cursor(C),
387 R
388 end,
389 lists:reverse(resultat_transaction(mnesia:transaction(F))).
390
391
392 % Renvoie les messages manquants pour la page P en sachant qu'il y a N message
393 % par page et que le dernier message que l'on possède est Id
394 messages(Id, N, P) ->
395 lists:filter(fun (M) -> M#minichat.id > Id end, messages(N, P)).
396
397
398 % Renvoie {ok, #minichat} (voir #minichat de euphorik_bd.hrl) à partir de son id.
399 message_by_id(Id) ->
400 resultat_transaction(mnesia:transaction(
401 fun() ->
402 case mnesia:read({minichat, Id}) of
403 [M] -> {ok, M#minichat{contenu = contenu_message(M)}};
404 _ -> erreur
405 end
406 end
407 )).
408
409
410 % Renvoie le contenu d'un message donnée en fonction du troll associé, à utiliser à l'intérieur d'une transaction.
411 % TODO : Cette fonction pourrait être remplacé par un "outer-join", est-ce possible avec qlc ?
412 contenu_message(E) ->
413 case mnesia:read({troll, E#minichat.troll_id}) of
414 [] -> E#minichat.contenu;
415 [T] -> E#minichat.contenu ++ T#troll.content
416 end.
417
418
419 % Renvoie une liste de message (voir #minichat de euphorik_bd.hrl) à partir d'une liste d'id (Ids).
420 % TODO : optimisations ? serait-ce du O(n) ?
421 % Bon de toutes façons on s'en fout c'est pas utilisé :)
422 messages_by_ids(Ids) ->
423 resultat_transaction(mnesia:transaction(
424 fun() ->
425 e(qlc:keysort(
426 #minichat.id,
427 q([E || E <- mnesia:table(minichat), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]),
428 [{order, ascending}]
429 ),[{tmpdir, ?KEY_SORT_TEMP_DIR}])
430 end
431 )).
432
433
434 % Est-ce qu'un message existe ? Renvoie un boolean.
435 % TODO : ya pas plus simple ?
436 message_existe(Id) ->
437 resultat_transaction(mnesia:transaction(fun() ->
438 length(e(q([E#minichat.id || E <- mnesia:table(minichat), E#minichat.id =:= Id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])) =:= 1
439 end)).
440
441
442 % Renvoie les reponses (utilisé normalement uniquement pendant le debug).
443 reponses() ->
444 F = fun() ->
445 e(q([E || E <- mnesia:table(reponse_minichat)]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
446 end,
447 resultat_transaction(mnesia:transaction(F)).
448
449
450 % Renvoie les messages auquel M_id répond.
451 parents(M_id) ->
452 resultat_transaction(mnesia:transaction(
453 fun() ->
454 e(q(
455 [M || R <- mnesia:table(reponse_minichat),
456 M <- mnesia:table(minichat),
457 R#reponse_minichat.repondant =:= M_id,
458 M#minichat.id =:= R#reponse_minichat.cible]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
459 end
460 )).
461
462
463 % Renvoie les message qui repondent à M_id
464 enfants(M_id) ->
465 resultat_transaction(mnesia:transaction(
466 fun() ->
467 e(q(
468 [M || R <- mnesia:table(reponse_minichat),
469 M <- mnesia:table(minichat),
470 R#reponse_minichat.cible =:= M_id,
471 M#minichat.id =:= R#reponse_minichat.repondant]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
472 end
473 )).
474
475
476 % Renvoie les id des parents d'un message M (les messages auquels répond M)
477 % ordrés du plus petit au plus grand..
478 % On évite d'utiliser qlc pour des raisons de performance
479 % @spec parents_id(integer()) -> [integer()]
480 parents_id(M_id) ->
481 resultat_transaction(mnesia:transaction(fun() ->
482 case mnesia:read({reponse_minichat, M_id}) of
483 Parents when is_list(Parents) ->
484 lists:sort(lists:map(
485 fun(#reponse_minichat{cible = Cible}) -> Cible end,
486 Parents
487 ));
488 _ -> []
489 end
490 end)).
491
492
493 % Renvoie les id des enfants d'un message M (les messages qui répondent à M)
494 % ordrés du plus petit au plus grand.
495 % @spec enfants_id(integer()) -> [integer()]
496 enfants_id(M_id) ->
497 resultat_transaction(mnesia:transaction(fun() ->
498 case mnesia:index_read(reponse_minichat, M_id, #reponse_minichat.cible) of
499 Enfants when is_list(Enfants) ->
500 lists:sort(lists:map(
501 fun(#reponse_minichat{repondant = Repondant}) -> Repondant end,
502 Enfants
503 ));
504 _ -> []
505 end
506 end)).
507
508
509 % Est-ce que le message Id_mess est une réponse d'une message de Id_user ?
510 % On evite d'utiliser qlc (ce qui était fait avant) pour des raisons de performance.
511 est_une_reponse_a_user(Id_user, Id_mess) ->
512 resultat_transaction(mnesia:transaction(
513 fun() ->
514 case mnesia:read({reponse_minichat, Id_mess}) of
515 [] -> false;
516 Cibles ->
517 lists:any(
518 fun(#reponse_minichat{cible = Cible}) ->
519 case mnesia:read({minichat, Cible}) of
520 [#minichat{auteur_id = Id_user}] -> true;
521 _ -> false
522 end
523 end,
524 Cibles
525 )
526 end
527 end
528 )).
529
530
531 % Est-ce que Id_user à répondu au message Id_mess
532 % On evite d'utiliser qlc (ce qui était fait avant) pour des raisons de performance.
533 a_repondu_a_message(Id_user, Id_mess) ->
534 resultat_transaction(mnesia:transaction(
535 fun() ->
536 case mnesia:index_read(reponse_minichat, Id_mess, #reponse_minichat.cible) of
537 [] -> false;
538 Reponses ->
539 lists:any(
540 fun(#reponse_minichat{repondant = Reponse}) ->
541 case mnesia:read({minichat, Reponse}) of
542 [#minichat{auteur_id = Id_user}] -> true;
543 _ -> false
544 end
545 end,
546 Reponses
547 )
548 end
549 end
550 )).
551
552
553 % Est-ce que Id_user possède Id_mess ?
554 possede_message(Id_user, Id_mess) ->
555 case mnesia:transaction(
556 fun() ->
557 e(q([E#minichat.auteur_id || E <- mnesia:table(minichat), E#minichat.id =:= Id_mess]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
558 end
559 ) of
560 {atomic, [Id_user | []]} -> true;
561 _ -> false
562 end.
563
564
565 % renvoie la liste des ip bannies
566 % liste de {ip, temps_restant(en minutes), Users} ou Users est une liste de {pseudo, login}
567 % TODO : déterminer la complexité de cette fonction. (Il n'y a pas d'index sur #user.last_ip)
568 list_ban() ->
569 resultat_transaction(mnesia:transaction(
570 fun() ->
571 Now = now(),
572 e(qlc:keysort(1, q([
573 {
574 IP#ip_table.ip,
575 euphorik_common:delta_date_minute(date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration), Now),
576 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}])
577 } ||
578 IP <- mnesia:table(ip_table),
579 if IP#ip_table.ban =:= undefined -> false; true -> date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration) > Now end
580 ])), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
581 end
582 )).
583
584
585 % Bannie une ip pour un certain temps (en minute).
586 ban(IP, Duration) ->
587 mnesia:transaction(
588 fun() ->
589 case mnesia:wread({ip_table, IP}) of
590 [IP_tuple] ->
591 mnesia:write(IP_tuple#ip_table{ban = now(), ban_duration = Duration});
592 _ ->
593 mnesia:write(#ip_table{ip = IP, ban = now(), ban_duration = Duration})
594 end
595 end
596 ).
597
598
599 % Débanni une ip
600 deban(IP) ->
601 ban(IP, 0).
602
603
604 % Renvoie soit {true, Temps} où Temps est le temps en minutes pendant lequel le user est encore banni
605 % ou false.
606 est_banni(User_id) ->
607 resultat_transaction(mnesia:transaction(
608 fun() ->
609 case e(q([
610 {IP#ip_table.ban, IP#ip_table.ban_duration} ||
611 U <- mnesia:table(user),
612 U#user.id =:= User_id,
613 IP <- mnesia:table(ip_table),
614 IP#ip_table.ip =:= U#user.last_ip
615 ]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
616 [{Ban, Ban_duration}] ->
617 Echeance = date_plus_minutes(Ban, Ban_duration),
618 Now = now(),
619 if Echeance < Now -> % l'échéance est passée
620 false;
621 true ->
622 {true, euphorik_common:delta_date_minute(Echeance, Now)}
623 end;
624 _ ->
625 false
626 end
627 end
628 )).
629
630
631 % Ban est une date tel que retourner par now().
632 % Ban_duration est un temps en minutes.
633 % retourne une date.
634 date_plus_minutes(Ban, Ban_duration) ->
635 Duration_sec = Ban_duration * 60,
636 {MegaSec, Sec, MicroSec} = Ban,
637 {MegaSec + if Sec + Duration_sec >= 1000000 -> 1; true -> 0 end,(Sec + Duration_sec) rem 1000000, MicroSec}.
638
639
640 % Si deux enregistrements consequtifs de la même IP sont fait en moins d'une seconde alors
641 % ip_table.nb_try_register est incrémenté de 1 sinon il est décrémenté de 1 (jusqu'a 0).
642 % Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure.
643 can_register(IP) ->
644 resultat_transaction(mnesia:transaction(
645 fun() ->
646 case e(q([I || I <- mnesia:table(ip_table), I#ip_table.ip =:= IP]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
647 [] ->
648 mnesia:write(#ip_table{ip = IP, date_last_try_register = now()}),
649 true;
650 [T] ->
651 Delta = euphorik_common:delta_date_ms(T#ip_table.date_last_try_register, now()),
652 if T#ip_table.nb_try_register =:= ?NB_MAX_FLOOD_REGISTER, Delta < ?TEMPS_BAN_FLOOD_REGISTER ->
653 false;
654 true ->
655 mnesia:write(#ip_table{
656 ip = IP,
657 date_last_try_register = now(),
658 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
659 }),
660 true
661 end
662 end
663 end
664 )).
665
666
667 % Renvoie tous les trolls
668 trolls() ->
669 resultat_transaction(mnesia:transaction(
670 fun() ->
671 e(qlc:keysort(#troll.id, q([T || T <- mnesia:table(troll)])), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
672 end
673 )).
674
675
676 % Renvoie les trolls manquants posté après Last_id.
677 trolls(Last_id) ->
678 resultat_transaction(mnesia:transaction(
679 fun() ->
680 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}])
681 end
682 )).
683
684
685 % Crée un nouveau troll.
686 % Renvoie l'id du nouveau troll
687 % ou max_troll_reached_per_user si le nombre de troll posté par l'utilisateur max a été atteind
688 % ou max_troll_reached si le nombre de troll posté max a été atteind
689 % ou user_unknown
690 put_troll(User_id, Content) ->
691 resultat_transaction(mnesia:transaction(
692 fun() ->
693 % control le nombre de troll déjà posté
694 Nb_troll_poste_par_user = length(e(q(
695 [
696 E#troll.id || E <- mnesia:table(troll),
697 E#troll.id_user =:= User_id,
698 E#troll.date_post =:= undefined
699 ]
700 ), [{tmpdir, ?KEY_SORT_TEMP_DIR}])),
701 Nb_troll_poste_total = length(e(q(
702 [
703 E#troll.id || E <- mnesia:table(troll),
704 E#troll.date_post =:= undefined
705 ]
706 ), [{tmpdir, ?KEY_SORT_TEMP_DIR}])),
707 User = user_by_id(User_id),
708 case User of
709 {ok, _} ->
710 if Nb_troll_poste_par_user >= ?NB_MAX_TROLL_WAITING_BY_USER ->
711 max_troll_reached_per_user;
712 Nb_troll_poste_total >= ?NB_MAX_TROLL_WAITING ->
713 max_troll_reached;
714 true ->
715 Id = nouvel_id(minichat),
716 mnesia:write(#troll{id = Id, id_user = User_id, date_create = now(), content = Content}),
717 Id
718 end;
719 _ ->
720 user_unknown
721 end
722 end
723 )).
724
725
726 % renvoie ok | erreur
727 mod_troll(Troll_id, Content) ->
728 mnesia:transaction(
729 fun() ->
730 case mnesia:wread({troll, Troll_id}) of
731 [Troll = #troll{date_post = undefined}] ->
732 mnesia:write(Troll#troll{content = Content});
733 _ ->
734 mnesia:abort("mod_troll: Troll inconnu ou déjà posté")
735 end
736 end
737 ).
738
739
740 del_troll(Troll_id) ->
741 mnesia:transaction(
742 fun() ->
743 case mnesia:wread({troll, Troll_id}) of
744 [#troll{date_post = undefined}] ->
745 mnesia:delete({troll, Troll_id});
746 _ ->
747 mnesia:abort("mod_troll: Troll inconnu ou déjà posté")
748 end
749 end
750 ).
751
752
753 troll_by_id(Troll_id) ->
754 resultat_transaction(mnesia:transaction(
755 fun() ->
756 case e(q([T || T <- mnesia:table(troll), T#troll.id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
757 [T] -> {ok, T};
758 _ ->
759 erreur
760 end
761 end
762 )).
763
764
765 % Renvoie le troll actuel qui se trouve sur la page principale.
766 % Renvois aucun si pas de troll courant.
767 current_troll() ->
768 resultat_transaction(mnesia:transaction(
769 fun() ->
770 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}]),
771 R = case qlc:next_answers(C, 1) of
772 [T] -> T;
773 _ -> aucun
774 end,
775 qlc:delete_cursor(C),
776 R
777 end
778 )).
779
780
781 % Elit un troll au hasard parmis les trolls en attente (leur date_post =:= undefined)
782 % Un message est posté par 'Sys' et le troll elu est lié à ce message
783 % met à jour sa date de post.
784 % renvoie plus_de_trolls si il n'y a aucun troll en attente.
785 elire_troll() ->
786 {A1,A2,A3} = now(),
787 random:seed(A1, A2, A3),
788 mnesia:transaction(
789 fun() ->
790 case e(q([T || T <- mnesia:table(troll), T#troll.date_post =:= undefined]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
791 [] ->
792 plus_de_trolls;
793 Trolls ->
794 Troll = lists:nth(random:uniform(length(Trolls)), Trolls),
795 Troll2 = Troll#troll{date_post = now()},
796 mnesia:write(Troll2),
797 nouveau_message_sys("Troll de la semaine : ", Troll2#troll.id)
798 end
799 end
800 ).
801
802
803 % Renvoie l'id du message associé au troll dont l'id est donnée.
804 % Renvoie undefined si il n'y en a pas.
805 message_id_associe(Troll_id) ->
806 resultat_transaction(mnesia:transaction(
807 fun() ->
808 case e(q([M#minichat.id || M <- mnesia:table(minichat), M#minichat.troll_id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
809 [Id] -> Id;
810 _ -> undefined
811 end
812 end
813 )).
814
815
816 % Renvoie le résultat d'une transaction (en décomposant le tuple fournit)
817 resultat_transaction({_, T}) ->
818 T.
819
820
821 % Renvoie un nouvel id pour une table donnée
822 nouvel_id(Table) ->
823 mnesia:dirty_update_counter(counter, Table, 1).
824