diff --git a/.gitmodules b/.gitmodules index cc438ba..9c75698 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "nitrogen"] path = nitrogen - url = git://github.com/rklophaus/nitrogen.git + url = http://github.com/rklophaus/nitrogen.git +[submodule "esmtp"] + path = esmtp + url = git://github.com/AlexanderWingard/esmtp.git \ No newline at end of file diff --git a/esmtp b/esmtp new file mode 160000 index 0000000..5958b8d --- /dev/null +++ b/esmtp @@ -0,0 +1 @@ +Subproject commit 5958b8d2cd2bd6253fb56a29ec7927f94dbd3922 diff --git a/site/apps.config b/site/apps.config new file mode 100644 index 0000000..d6ad9a8 --- /dev/null +++ b/site/apps.config @@ -0,0 +1 @@ +[{esmtp, [{smarthost, {"mailout.comhem.se", 25}}]}]. \ No newline at end of file diff --git a/site/include/umts_db.hrl b/site/include/umts_db.hrl index 9252c7b..3b8d8d5 100644 --- a/site/include/umts_db.hrl +++ b/site/include/umts_db.hrl @@ -1,4 +1,10 @@ --record(users, {id, name, password, display, lastlogin}). +-record(users, {id, name, password, display, email, lastlogin}). -record(wtts, {id, timestamp, wanters = ordsets:new(), havers = ordsets:new()}). -record(auto_increment, {table, key}). -record(cards, {id, name, color=[]}). +-record(events, {time, event}). +-record(login_event, {user}). +-record(register_event, {user}). +-record(wtt_event, {user, card, kind}). + +%% mnesia:transform_table(users, fun({users, Id, Name, Password, Display}) -> {users, Id, Name, Password, Display, ""} end, [id, name, password, display, email]). diff --git a/site/src/eventlog.erl b/site/src/eventlog.erl new file mode 100644 index 0000000..7d85276 --- /dev/null +++ b/site/src/eventlog.erl @@ -0,0 +1,20 @@ +-module(eventlog). +-compile(export_all). +-include_lib("nitrogen/include/wf.hrl"). + +-include("umts_db.hrl"). + +main() -> + #template{file = "./templates/bare.html"}. + +title() -> + "Recent events". + +body() -> + #container_12{body = [#grid_8{ + alpha=true, + prefix=2, + suffix=2, + omega=true, + body=umts_eventlog:get_events() + }]}. diff --git a/site/src/index.erl b/site/src/index.erl index a4398ac..0299618 100644 --- a/site/src/index.erl +++ b/site/src/index.erl @@ -5,11 +5,11 @@ -include("umts_db.hrl"). main() -> - case wf:user() of - undefined -> + case is_integer(wf:user()) orelse login:cookie_login() of + false -> wf:redirect("/login"); - _User -> - wf:state(sort, [{havers,true},{wanters, + true -> + wf:state(sort, [{havers,true},{wanters, true},{color,"U"},{color,"G"},{color,"B"},{color,"W"},{color,"R"},{color,"A"}]), #template { file="./templates/bare.html" } end. @@ -102,7 +102,7 @@ update_sortlist(Color, Id)-> C. handle_event(logout) -> - wf:logout(), + login:logout(), wf:redirect("/login"); handle_event(search) -> Request = wf:q(search), @@ -119,7 +119,9 @@ handle_event(show_user)-> handle_event({wtt, Callback, Id}) -> %% TODO: Some more security here? - umts_db:Callback(Id, wf:user()), + User = wf:user(), + umts_db:Callback(Id, User), + umts_eventlog:log_wtt(User, Id, Callback), Card = card(umts_db:get_card(Id)), wf:replace("srch" ++ Id, Card#panel{id = "srch" ++ Id}), %% TODO: Do we really need to redraw everything here? diff --git a/site/src/login.erl b/site/src/login.erl index c9ff1d7..c63a81b 100644 --- a/site/src/login.erl +++ b/site/src/login.erl @@ -4,6 +4,7 @@ -include("umts_db.hrl"). + main() -> #template { file="./templates/bare.html" }. title() -> "Login". @@ -31,12 +32,23 @@ inner_body() -> #br{}, #button{text = "Login", postback = login}, #button{text = "Register", postback = register}, - #lightbox{id = lb, - body = [#panel{id = confirmbox, + #br{}, + #link{text = "Forgot my password", postback = show_forgot}, + #lightbox{id = lbregister, + body = [#panel{class = "confirmbox", body = ["Confirm password:", #password{id = password2, postback = confirm}, + "E-mail:", + #textbox{id = email}, #button{text = "Confirm", postback = confirm}, - #button{text = "Cancel", postback = cancel_confirm}]}], + #button{text = "Cancel", postback = cancel_lb}]}], + style = "display: none;"}, + #lightbox{id = lbforgot, + body = [#panel{class = "confirmbox", + body = ["E-mail:", + #textbox{id = forgotemail}, + #button{text = "Confirm", postback = forgot}, + #button{text = "Cancel", postback = cancel_lb}]}], style = "display: none;"} ]. @@ -47,7 +59,11 @@ event(login) -> not_found -> wf:flash("Incorrect username or password"); Id -> + umts_eventlog:log_login(Id), wf:user(Id), + %% Cookie stays for 2 months + wf:cookie("username", Username, "", 90000), + wf:cookie("password", Password, "", 90000), wf:redirect("start") end; event(register) -> @@ -58,25 +74,69 @@ event(register) -> true -> wf:flash("Please enter a username and password to register"); false -> - wf:wire(lb, #show{}) + wf:wire(lbregister, #show{}) end; event(confirm) -> Username = wf:q(username), Password = wf:q(password), Password2 = wf:q(password2), - wf:wire(lb, #hide{}), - case Password == Password2 of - true -> - case umts_db:insert_user(Username, Password) of + Email = wf:q(email), + ValidEmail = validator_is_email:validate(null, Email), + wf:wire(lbregister, #hide{}), + if Password /= Password2 -> + wf:flash("Password doesn't match"); + not ValidEmail -> + wf:flash("Please enter a valid email"); + true -> + case umts_db:insert_user(Username, Password, Email) of {ok, NewID} -> + umts_eventlog:log_register(NewID), wf:user(NewID), umts_db:update_lastlogin(NewID, now()), wf:redirect("/"); {fault, exists} -> wf:flash("Username already exists") - end; - false -> - wf:flash("Password doesn't match") + end + end; +event(forgot) -> + Email = wf:q(forgotemail), + wf:wire(lbforgot, #hide{}), + case umts_db:find_user_email(Email) of + [] -> + wf:flash("No user with that email found"); + Emails -> + lists:foreach(fun send_forgotmail/1, Emails), + wf:flash("Information sent to " ++ Email) end; +event(show_forgot) -> + wf:wire(lbforgot, #show{}); event(cancel_confirm) -> - wf:wire(lb, #hide{}). + wf:wire(lb, #hide{}); +event(cancel_lb) -> + wf:wire(lbregister, #hide{}), + wf:wire(lbforgot, #hide{}). + +send_forgotmail(User) -> + Msg = esmtp_mime:msg(User#users.email, + "alexander.wingard@gmail.com", + "UMTS login information", + "Username: " ++ User#users.name ++ "\nPassword: " ++ User#users.password), + esmtp:send(Msg). + +cookie_login() -> + Username = wf:cookie("username"), + Password = wf:cookie("password"), + case umts_db:login(Username, Password) of + not_found -> + false; + Id -> + wf:user(Id), + umts_db:update_lastlogin(Id, now()), + wf:redirect("start"), + true + end. + +logout() -> + wf:cookie("username", undefined), + wf:cookie("password", undefined), + wf:logout(). diff --git a/site/src/umts_db.erl b/site/src/umts_db.erl index 946da7b..90996de 100644 --- a/site/src/umts_db.erl +++ b/site/src/umts_db.erl @@ -15,27 +15,13 @@ reinstall() -> create_tables(). create_tables() -> - {atomic, ok} = mnesia:create_table(users, [{attributes, record_info(fields, users)}, {disc_copies,[node()]}]), - {atomic, ok} = mnesia:create_table(wtts, [{attributes, record_info(fields, wtts)}, {disc_copies,[node()]}]), - {atomic, ok} = mnesia:create_table(cards, [{attributes, record_info(fields, cards)}, {disc_copies,[node()]}]), - {atomic, ok} = mnesia:create_table(auto_increment, [{attributes, record_info(fields, auto_increment)}, {disc_copies,[node()]}]). - -transform() -> - transform(0). - -transform(0) -> - T = fun({users, ID, Name, Password}) -> - #users{id = ID, - name = Name, - password = Password, - display = Name} - end, - mnesia:transform_table(users, T, record_info(fields, users)), - transform(1); -transform(_) -> - ok. + mnesia:create_table(users, [{attributes, record_info(fields, users)}, {disc_copies,[node()]}]), + mnesia:create_table(wtts, [{attributes, record_info(fields, wtts)}, {disc_copies,[node()]}]), + mnesia:create_table(cards, [{attributes, record_info(fields, cards)}, {disc_copies,[node()]}]), + mnesia:create_table(events, [{attributes, record_info(fields, events)}, {disc_copies, [node()]}]), + mnesia:create_table(auto_increment, [{attributes, record_info(fields, auto_increment)}, {disc_copies,[node()]}]). -insert_user(Name, Password) -> +insert_user(Name, Password, Email) -> Q = qlc:q([U#users.id || U <- mnesia:table(users), U#users.name == Name]), T = fun() -> @@ -45,7 +31,8 @@ insert_user(Name, Password) -> ok = mnesia:write(#users{id = NewID, name = string:to_lower(Name), password = Password, - display = Name}), + display = Name, + email = string:to_lower(Email)}), {ok, NewID}; [_Existing] -> {fault, exists} @@ -70,6 +57,13 @@ get_user(Id) -> {atomic, Result} = mnesia:transaction(T), Result. +find_user_email(Email) -> + T = fun() -> + mnesia:match_object(#users{email = string:to_lower(Email), _ = '_'}) + end, + {atomic, Result} = mnesia:transaction(T), + Result. + get_users()-> Q = qlc:q([U || U <- mnesia:table(users)]), T = fun() -> qlc:e(Q) end, @@ -146,20 +140,6 @@ get_wtts(Id) -> {atomic, Res} = mnesia:transaction(T), Res. -get_only_havers()-> - Q = qlc:q([W || W <- mnesia:table(wtts), - W#wtts.wanters == []]), - T = fun()-> qlc:e(Q) end, - {atomic, Res} = mnesia:transaction(T), - Res. - -get_only_wanters()-> - Q = qlc:q([W || W <- mnesia:table(wtts), - W#wtts.havers == []]), - T = fun()-> qlc:e(Q) end, - {atomic, Res} = mnesia:transaction(T), - Res. - sort(Colors)-> [C || C<-all_wtts(), X <- Colors, @@ -179,6 +159,9 @@ sort2(L)-> Colors = proplists:get_all_values(color, L), [C || C <- Res, X<-Colors, lists:member(X,C#cards.color)]. +login(Name, Password) when Name == undefined; + Password == undefined -> + not_found; login(Name, Password) -> Q = qlc:q([U#users.id || U <- mnesia:table(users), U#users.name == string:to_lower(Name), @@ -206,6 +189,7 @@ get_card(Id) -> {atomic, Result} = mnesia:transaction(T), Result. + all(Table) -> Q = qlc:q([R || R <- mnesia:table(Table)]), T = fun() -> qlc:e(Q) end, diff --git a/site/src/umts_eventlog.erl b/site/src/umts_eventlog.erl new file mode 100644 index 0000000..88a4758 --- /dev/null +++ b/site/src/umts_eventlog.erl @@ -0,0 +1,53 @@ +-module(umts_eventlog). +-compile(export_all). +-include_lib("nitrogen/include/wf.hrl"). + +-include("umts_db.hrl"). + +log_login(User) -> + log_event(#login_event{user = User}). + +log_register(User) -> + log_event(#register_event{user = User}). + +log_wtt(User, Card, Kind) -> + log_event(#wtt_event{user = User, card = Card, kind = Kind}). + +log_event(Event) -> + T = fun() -> + mnesia:write(#events{time = now(), event = Event}) + end, + {atomic, ok} = mnesia:transaction(T). + +get_events() -> + Items = lists:flatmap(fun format_event/1, lists:reverse(lists:keysort(#events.time, umts_db:all(events)))), + #list{body = Items}. + +format_event(Event) -> + case format_event_event(Event#events.event) of + not_ok -> + []; + Formated -> + [#listitem{text = wf:f("~w: ~s", [calendar:now_to_local_time(Event#events.time), + Formated])}] + end. + +format_event_event(#wtt_event{user = UserID, card = CardID, kind = Kind}) -> + User = umts_db:get_user(UserID), + Card = umts_db:get_card(CardID), + Action = case Kind of + add_wanter -> + "now wants"; + del_wanter -> + "no longer wants"; + add_haver -> + "now haves"; + del_haver -> + "no longer haves" + end, + wf:f("~s ~s ~s", [User#users.display, Action, Card#cards.name]); +format_event_event(#login_event{user = UserID}) -> + User = umts_db:get_user(UserID), + wf:f("~s logged in", [User#users.display]); +format_event_event(Event) -> + not_ok. diff --git a/site/start.bat b/site/start.bat index b1a9b11..5f5ae49 100644 --- a/site/start.bat +++ b/site/start.bat @@ -1 +1 @@ -"c:\Program Files\erl5.8.1\bin\werl.exe" -sname umts -pa "./site/ebin" -pa "../nitrogen/apps/nitrogen/ebin" -pa "../nitrogen/apps/simple_bridge/ebin" -pa "../nitrogen/apps/nprocreg/ebin" -pa "./ebin" \ No newline at end of file +"c:\Program Files\erl5.8.1\bin\werl.exe" -config apps -sname umts -pa "./site/ebin" -pa "../nitrogen/apps/nitrogen/ebin" -pa "../nitrogen/apps/simple_bridge/ebin" -pa "../nitrogen/apps/nprocreg/ebin" -pa "../esmtp/ebin" -pa "./ebin" -eval "lists:foreach(fun application:start/1, [sasl, nprocreg, esmtp, umts])." \ No newline at end of file diff --git a/site/static/css/style.css b/site/static/css/style.css index 695b24f..58a9198 100644 --- a/site/static/css/style.css +++ b/site/static/css/style.css @@ -88,7 +88,7 @@ a:hover, a:active { margin-left: 290px; } -.wfid_confirmbox { +.confirmbox { padding: 25px; background-color: white; border-radius: 15px;