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