Permalink
Browse files

first commit

  • Loading branch information...
0 parents commit adf757e783407df8b370d517cc4485be0de95449 Evan Miller committed Nov 27, 2011
@@ -0,0 +1,16 @@
+ERL=erl
+APP=cb_tutorial
+
+all:
+ $(ERL) -pa /Users/emiller/src/ChicagoBoss/ebin -eval 'boss_load:load_all_modules_and_emit_app_file($(APP), "ebin")' -s init stop -noshell
+
+clean:
+ rm -fv ebin/*.beam
+ rm -fv ebin/$(APP).app
+
+update_po:
+ $(ERL) -pa ebin -pa ../ChicagoBoss/ebin -eval 'boss_load:load_models("ebin")' -eval 'boss_lang:update_po()' -s init stop -noshell
+
+.PHONY: test
+test:
+ $(ERL) -pa /Users/emiller/src/ChicagoBoss/ebin -run boss_web_test -noshell
1 README
@@ -0,0 +1 @@
+This is the example application created in Chicago Boss: A Rough Introduction
@@ -0,0 +1,16 @@
+[{boss, [
+ {applications, [cb_tutorial]},
+ {db_host, "localhost"},
+ {db_port, 1978},
+ {db_adapter, mock},
+ {log_dir, "log"},
+ {server, mochiweb},
+ {port, 8001},
+ {session_adapter, mock},
+ {session_key, "_boss_session"},
+ {session_exp_time, 525600}
+]},
+{ cb_tutorial, [
+ {base_url, "/"}
+]}
+].
@@ -0,0 +1,8 @@
+{application, cb_tutorial, [
+ {description, "My Awesome Web Framework"},
+ {vsn, "0.0.1"},
+ {modules, []},
+ {registered, []},
+ {applications, [kernel, stdlib, crypto, boss]},
+ {env, []}
+ ]}.
@@ -0,0 +1,13 @@
+% Routes file.
+
+% Formats:
+% {"/some/route", {"Controller", "Action"}}.
+% {"/some/route", {"Controller", "Action", [{id, "42"}]}}.
+% {404, {"Controller", "Action"}}.
+% {404, {"Controller", "Action", [{id, "42"}]}}.
+
+% Front page
+% {"/", {"hello", "world"}}.
+
+% 404 File Not Found handler
+% {404, {"hello", "goodbye"}}.
@@ -0,0 +1,103 @@
+-module(cb_tutorial_news).
+
+-export([init/0]).
+
+% This script is first executed at server startup and should
+% return a list of WatchIDs that should be cancelled if the
+% script is ever reloaded (for example, via the admin application).
+init() ->
+ {ok, WatchId} = boss_news:watch("messages",
+ fun(created, NewMessage) ->
+ error_logger:info_msg("New message! ~p~n",
+ [NewMessage:message_contents()]),
+ boss_mq:push("new-messages", NewMessage);
+ (deleted, OldMessage) ->
+ error_logger:info_msg("Message go poof! ~p~n",
+ [OldMessage:message_contents()])
+ end),
+ {ok, [WatchId]}.
+
+%%%%%%%%%%% Ideas
+% boss_news:watch("user-42.*",
+% fun
+% (updated, {Donald, 'location', OldLocation, NewLocation}) ->
+% ;
+% (updated, {Donald, 'email_address', OldEmail, NewEmail})
+% end),
+%
+% boss_news:watch("user-*.status",
+% fun(updated, {User, 'status', OldStatus, NewStatus}) ->
+% Followers = User:followers(),
+% lists:map(fun(Follower) ->
+% Follower:notify_status_update(User, NewStatus)
+% end, Followers)
+% end),
+%
+% boss_news:watch("users",
+% fun
+% (created, NewUser) ->
+% boss_mail:send(?WEBSITE_EMAIL_ADDRESS,
+% ?ADMINISTRATOR_EMAIL_ADDRESS,
+% "New account!",
+% "~p just created an account!~n",
+% [NewUser:name()]);
+% (deleted, OldUser) ->
+% ok
+% end),
+%
+% boss_news:watch("forum_replies",
+% fun
+% (created, Reply) ->
+% OrignalPost = Reply:original_post(),
+% OriginalAuthor = OriginalPost:author(),
+% case OriginalAuthor:is_online() of
+% true ->
+% boss_mq:push(OriginalAuthor:comet_channel(), <<"Someone replied!">>);
+% false ->
+% case OriginalAuthor:likes_email() of
+% true ->
+% boss_mail:send("website@blahblahblah",
+% OriginalAuthor:email_address(),
+% "Someone replied!"
+% "~p has replied to your post on ~p~n",
+% [(Reply:author()):name(), OriginalPost:title()]);
+% false ->
+% ok
+% end
+% end;
+% (_, _) -> ok
+% end),
+%
+% boss_news:watch("forum_categories",
+% fun
+% (created, NewCategory) ->
+% boss_mail:send(?WEBSITE_EMAIL_ADDRESS,
+% ?ADMINISTRATOR_EMAIL_ADDRESS,
+% "New category: "++NewCategory:name(),
+% "~p has created a new forum category called \"~p\"~n",
+% [(NewCategory:created_by()):name(), NewCategory:name()]);
+% (_, _) -> ok
+% end),
+%
+% boss_news:watch("forum_category-*.is_deleted",
+% fun
+% (updated, {ForumCategory, 'is_deleted', false, true}) ->
+% ;
+% (updated, {ForumCategory, 'is_deleted', true, false}) ->
+% end).
+
+% Invoking the API directly:
+%boss_news:deleted("person-42", OldAttrs),
+%boss_news:updated("person-42", OldAttrs, NewAttrs),
+%boss_news:created("person-42", NewAttrs)
+
+% Invoking the API via HTTP (with the admin application installed):
+% POST /admin/news_api/deleted/person-42
+% old[status] = something
+
+% POST /admin/news_api/updated/person-42
+% old[status] = blah
+% new[status] = barf
+
+% POST /admin/news_api/created/person-42
+% new[status] = something
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
@@ -0,0 +1,71 @@
+-module(cb_tutorial_account_controller, [Req, SessionID]).
+-compile(export_all).
+
+login('GET', []) ->
+ {ok, [{redirect, Req:header(referer)}]};
+login('POST', []) ->
+ Email = Req:post_param("email"),
+ Password = Req:post_param("password"),
+ Redirect = Req:post_param("redirect"),
+ Result = case boss_db:find(account, [email = Email]) of
+ [Account] ->
+ case Account:check_password(Password) of
+ true ->
+ boss_session:set_session_data(SessionID,
+ "account",
+ Account:id()),
+ true;
+ false -> false
+ end;
+ _ -> false
+ end,
+ case Result of
+ true -> {redirect, Req:post_param("redirect")};
+ false ->
+ {ok, [{redirect, Redirect}, {email, Email},
+ {errors, ["Invalid email/password combination"]}]}
+ end.
+
+logout('GET', []) ->
+ boss_session:remove_session_data(SessionID, "account"),
+ {redirect, "/"}.
+
+create('GET', []) ->
+ ok;
+create('POST', []) ->
+ Email = Req:post_param("email"),
+ Password1 = Req:post_param("password1"),
+ Password2 = Req:post_param("password2"),
+ ValidationTests = [{length(Password1) >= 6,
+ "Password must be at least 6 characters!"},
+ {Password1 =:= Password2,
+ "Passwords didn't match!"},
+ {boss_db:count(account, [email = Email]) =:= 0,
+ "An account with that email exists"}],
+ ValidationFailures = lists:foldr(fun
+ ({true, _}, Acc) -> Acc;
+ ({false, Message}, Acc) -> [Message|Acc]
+ end, [], ValidationTests),
+ Account = boss_record:new(account, [{email, Email}]),
+ ErrorList = case ValidationFailures of
+ [] ->
+ PasswordHash = Account:hash_for_password(Password1),
+ Account1 = Account:set(password_hash, PasswordHash),
+ case Account1:save() of
+ {ok, SavedAccount} ->
+ boss_session:set_session_data(SessionID,
+ "account", SavedAccount:id()),
+ [];
+ {error, List} -> List
+ end;
+ _ -> ValidationFailures
+ end,
+ case ErrorList of
+ [] -> {redirect, [{controller, "message"},
+ {action, "hello"}]};
+ _ -> {ok, [{errors, ErrorList}, {account, Account}]}
+ end.
+
+view('GET', [Id]) ->
+ Account = boss_db:find(Id),
+ {ok, [{account, Account}]}.
@@ -0,0 +1,39 @@
+-module(cb_tutorial_message_controller, [Req, SessionID]).
+-compile(export_all).
+
+before_(_) ->
+ case boss_session:get_session_data(SessionID, "account") of
+ undefined ->
+ {redirect, [{controller, "account"},
+ {action, "login"}]};
+ AccountID ->
+ {ok, Req:peer_ip() =:= {127, 0, 0, 1}}
+ end.
+
+hello('GET', [], IsLocal) ->
+ Messages = boss_db:find(message, []),
+ {ok, [{messages, Messages}, {is_local, IsLocal}]};
+hello('POST', [], IsLocal) ->
+ MessageContents = Req:post_param("message_contents"),
+ AccountID = boss_session:get_session_data(SessionID, "account"),
+ NewMessage = message:new(id, MessageContents, AccountID),
+ case NewMessage:save() of
+ {ok, SavedMessage} ->
+ {redirect, [{action, "hello"}]};
+ {error, ErrorList} ->
+ {ok, [{errors, ErrorList}, {new_msg, NewMessage}]}
+ end.
+
+goodbye('POST', [], true) ->
+ boss_db:delete(Req:post_param("message_id")),
+ {redirect, [{action, "hello"}]}.
+
+pull('GET', [LastTimestamp]) ->
+ {ok, Timestamp, Messages} = boss_mq:pull("new-messages",
+ list_to_integer(LastTimestamp)),
+ {json, [{timestamp, Timestamp}, {messages, Messages}]}.
+
+live('GET', []) ->
+ Messages = boss_db:find(message, []),
+ Timestamp = boss_mq:now("new-messages"),
+ {ok, [{messages, Messages}, {timestamp, Timestamp}]}.
@@ -0,0 +1,8 @@
+-module(cb_tutorial_incoming_mail_controller).
+-compile(export_all).
+
+authorize_(User, DomainName, IPAddress) ->
+ true.
+
+% post(FromAddress, Message) ->
+% ok.
@@ -0,0 +1,12 @@
+-module(cb_tutorial_outgoing_mail_controller).
+-compile(export_all).
+
+%% See http://www.chicagoboss.org/api-mail-controller.html for what should go in here
+
+test_message(FromAddress, ToAddress, Subject) ->
+ Headers = [
+ {"Subject", Subject},
+ {"To", ToAddress},
+ {"From", FromAddress}
+ ],
+ {ok, FromAddress, ToAddress, Headers, [{address, ToAddress}]}.
@@ -0,0 +1,14 @@
+-module(account, [Id, Email, PasswordHash]).
+-compile(export_all).
+-has({messages, many}).
+
+hash_for_password(Password) ->
+ PasswordSalt = mochihex:to_hex(erlang:md5(Email)),
+ mochihex:to_hex(erlang:md5(PasswordSalt ++ Password)).
+
+check_password(Password) ->
+ hash_for_password(Password) =:= PasswordHash.
+
+validation_tests() ->
+ [{fun() -> string:chr(Email, $@) > 0 end,
+ "Email must contain an @ symbol!"}].
@@ -0,0 +1,9 @@
+-module(message, [Id, MessageContents, AccountId]).
+-compile(export_all).
+-belongs_to(account).
+
+validation_tests() ->
+ [{fun() -> length(MessageContents) > 0 end,
+ "Message must be non-empty!"},
+ {fun() -> length(MessageContents) =< 140 end,
+ "Message must be 140 characters or fewer!"}].
@@ -0,0 +1,10 @@
+{% list_errors errors=errors %}
+
+<form method="post">
+Email address: <input name="email"
+ value="{% if account %}{{ account.email }}{% endif %}"><br>
+Password once: <input name="password1" type="password"><br>
+Password again: <input name="password2" type="password"><br>
+<input type="hidden" name="redirect" value="{{ redirect }}">
+<input type="submit">
+</form>
@@ -0,0 +1,8 @@
+{% list_errors errors=errors %}
+
+<form method="post">
+Email address: <input name="email"
+ value="{% if email %}{{ email }}{% endif %}"><br>
+Password: <input name="password" type="password"><br><input type="submit">
+</form>
+<a href="{% url action="create" %}">Create an account</a>
@@ -0,0 +1,7 @@
+<h1>{{ account.email }}</h1>
+All messages:
+<ul>
+ {% for msg in account.messages %}
+ <li>{{ msg.message_contents }}
+ {% endfor %}
+</ul>
@@ -0,0 +1,7 @@
+{% if errors %}
+<ul>
+ {% for error in errors %}
+ <li><font color=red>{{ error }}</font>
+ {% endfor %}
+</ul>
+{% endif %}
@@ -0,0 +1,34 @@
+{% if errors %}
+{% list_errors errors=errors %}
+{% else %}
+<ul>
+ {% if messages %}
+ {% for msg in messages %}
+ <li>{{ msg.message_contents }}
+ {% if msg.account %}--<a href="{% url controller="account" action="view" id=msg.account.id %}">{{ msg.account.email }}</a>{% endif %}
+ {% endfor %}
+ {% else %}
+ <li>No messages!
+ {% endif %}
+</ul>
+{% endif %}
+
+<form method="post">
+ Enter a new message:
+<textarea name="message_contents">
+{% if new_msg %}{{ new_msg.message_contents }}{% endif %}
+</textarea>
+ <input type="submit">
+</form>
+
+{% if is_local %}
+<form method="post" action="{% url action="goodbye" %}">
+ Delete:
+ <select name="message_id">
+ {% for msg in messages %}
+ <option value="{{ msg.id }}">{{ msg.message_contents }}
+ {% endfor %}
+ </select>
+ <input type="submit">
+</form>
+{% endif %}
Oops, something went wrong.

0 comments on commit adf757e

Please sign in to comment.