diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..cf563fc
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "ChicagoBoss"]
+ path = ChicagoBoss
+ url = https://github.com/evanmiller/ChicagoBoss.git
diff --git a/ChicagoBoss b/ChicagoBoss
new file mode 160000
index 0000000..16603b5
--- /dev/null
+++ b/ChicagoBoss
@@ -0,0 +1 @@
+Subproject commit 16603b5ee3126c80a52d364a0afa907503a3b679
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..f1a2542
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: ./run.sh
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..48f25a2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+Running ChicagoBoss (erlang web framework) on Heroku
+----------------------------------------------------
+
+N.B. This process was an initial attempt and is subject to change :)
+
+To create a new project from scratch:
+
+1. Create a top level project directory. Add an empty ebin/ directory.
+ - (N.B. also create a dummy file to commit to git)
+2.
+ git init; git submodule add https://github.com/evanmiller/ChicagoBoss.git ChicagoBoss
+3.
+ cd ChicagoBoss; make; make app PROJECT=myapp
+4. Add Procfile, run.sh from this project - edit project name in run.sh
+5. Edit myapp/init.sh - add the following sections: (running erl with the -sname param causes heroku startup to fail)
+
+ 'start-no-sname')
+ # Start Boss in production mode with no -sname parameter
+ echo "starting boss in production mode..."
+ START=$(./rebar boss c=start_cmd|grep -v "==>"|perl -pe 's/-sname\s+\S+//')
+ $START
+ ;;
+
+ 'start-dev-no-sname')
+ # Start Boss in development mode
+ START_DEV=$(./rebar boss c=start_dev_cmd|grep -v "==>"|perl -pe 's/-sname\s+\S+//')
+ $START_DEV
+ ;;
+
+6. Deploy! (git add .; git push heroku master)
+
+
+
+A few caveats:
+- This is based on using the heroku supplied erlang buildpack at: https://github.com/heroku/heroku-buildpack-erlang
+- A better solution would be to modify the buildpack to build CB at the compile stage (as opposed to the run stage)
+- As such, the above solution may crash at the first deploy due to the app not starting within 60secs. Run:
+ heroku logs -t
+ in a seperate window to keep an eye on the deployment/startup process.
+
+
diff --git a/cb-heroku.iml b/cb-heroku.iml
new file mode 100644
index 0000000..ef582b1
--- /dev/null
+++ b/cb-heroku.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ebin/.empty b/ebin/.empty
new file mode 100644
index 0000000..0c42493
--- /dev/null
+++ b/ebin/.empty
@@ -0,0 +1 @@
+This ebin dir is empty - it exists to convince Heroku that this is an Erlang app.
\ No newline at end of file
diff --git a/myapp/boss.config b/myapp/boss.config
new file mode 100644
index 0000000..56117e1
--- /dev/null
+++ b/myapp/boss.config
@@ -0,0 +1,19 @@
+[{boss, [
+ {path, "/Users/vorn/dev/erl/cb-heroku/ChicagoBoss"},
+ {vm_cookie, "abc123"},
+ {applications, [myapp]},
+ {db_host, "localhost"},
+ {db_port, 1978},
+ {db_adapter, mock},
+ {log_dir, "log"},
+ {server, misultin},
+ {port, 8001},
+ {session_adapter, mock},
+ {session_key, "_boss_session"},
+ {session_exp_time, 525600}
+]},
+{ myapp, [
+ {path, "../myapp"},
+ {base_url, "/"}
+]}
+].
\ No newline at end of file
diff --git a/myapp/init-dev.sh b/myapp/init-dev.sh
new file mode 100755
index 0000000..2c7e57d
--- /dev/null
+++ b/myapp/init-dev.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+#
+# Chicago Boss Dev Init System
+# easy start dev server (most common task)
+
+cd `dirname $0`
+
+./init.sh start-dev
diff --git a/myapp/init.sh b/myapp/init.sh
new file mode 100755
index 0000000..aff8eec
--- /dev/null
+++ b/myapp/init.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env sh
+#
+# Chicago Boss Init System
+#
+# @author: Jose Luis Gordo Romero
+#
+# -------------------------------------------------------------------
+# The shell commands are automatically generated by the boss rebar
+# plugin/driver, all configuration params and paths are in boss.config
+# -------------------------------------------------------------------
+
+cd `dirname $0`
+
+case "${1:-''}" in
+ 'start')
+ # Start Boss in production mode
+ echo "starting boss in production mode..."
+ START=$(./rebar boss c=start_cmd|grep -v "==>")
+ $START
+ ;;
+
+ 'start-dev')
+ # Start Boss in development mode
+ START_DEV=$(./rebar boss c=start_dev_cmd|grep -v "==>")
+ $START_DEV
+ ;;
+
+ 'start-standalone')
+ # Start Boss in production mode with no -sname parameter
+ echo "starting boss in production mode..."
+ START=$(./rebar boss c=start_cmd|grep -v "==>"|perl -pe 's/-sname\s+\S+//')
+ $START
+ ;;
+
+ 'start-dev-standalone')
+ # Start Boss in development mode
+ START_DEV=$(./rebar boss c=start_dev_cmd|grep -v "==>"|perl -pe 's/-sname\s+\S+//')
+ $START_DEV
+ ;;
+
+ 'stop')
+ # Stop Boss daemon
+ echo "stopping boss..."
+ STOP=$(./rebar boss c=stop_cmd|grep -v "==>")
+ # After hours of shell quoting problems with the erl command,
+ # eval with the command quoted works!!!
+ eval "$STOP"
+ ;;
+
+ 'reload')
+ # Boss hot code reload <-- only the actual node, not the entire cluster
+ echo "Hot code reload, (WARN: Only this node)"
+ RELOAD=$(./rebar boss c=reload_cmd|grep -v "==>")
+ eval "$RELOAD"
+ ;;
+
+ 'restart')
+ # Boss complete restart
+ echo "Restarting (stop-start) boss..."
+ $0 stop
+ $0 start
+ ;;
+ *)
+ echo "Chicago Boss Boot System"
+ echo "Usage: $SELF start|start-dev|stop|reload|restart"
+ exit 1
+ ;;
+esac
\ No newline at end of file
diff --git a/myapp/priv/init/myapp_01_news.erl b/myapp/priv/init/myapp_01_news.erl
new file mode 100644
index 0000000..c5dfc39
--- /dev/null
+++ b/myapp/priv/init/myapp_01_news.erl
@@ -0,0 +1,97 @@
+-module(myapp_01_news).
+
+-export([init/0, stop/1]).
+
+% This script is first executed at server startup and should
+% return a list of WatchIDs that should be cancelled in the stop
+% function below (stop is executed if the script is ever reloaded).
+init() ->
+ {ok, []}.
+
+stop(ListOfWatchIDs) ->
+ lists:map(fun boss_news:cancel_watch/1, ListOfWatchIDs).
+
+%%%%%%%%%%% 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
diff --git a/myapp/priv/myapp.routes b/myapp/priv/myapp.routes
new file mode 100644
index 0000000..c9f51f3
--- /dev/null
+++ b/myapp/priv/myapp.routes
@@ -0,0 +1,20 @@
+% Routes file.
+
+% Formats:
+% {"/some/route", [{controller, "Controller"}, {action, "Action"}]}.
+% {"/some/route", [{controller, "Controller"}, {action, "Action"}, {id, "42"}]}.
+% {"/some/route", [{application, some_app}, {controller, "Controller"}, {action, "Action"}, {id, "42"}]}.
+% {404, [{controller, "Controller"}, {action, "Action"}]}.
+% {404, [{controller, "Controller"}, {action, "Action"}, {id, "42"}]}.
+% {404, [{application, some_app}, {controller, "Controller"}, {action, "Action"}, {id, "42"}]}.
+%
+% Note that routing across applications results in a 302 redirect.
+
+% Front page
+% {"/", [{controller, "world"}, {action, "hello"}]}.
+
+% 404 File Not Found handler
+% {404, [{controller, "world"}, {action, "lost"}]}.
+
+% 500 Internal Error handler (only invoked in production)
+% {500, [{controller, "world"}, {action, "calamity"}]}.
diff --git a/myapp/priv/rebar/boss_plugin.erl b/myapp/priv/rebar/boss_plugin.erl
new file mode 100644
index 0000000..b469345
--- /dev/null
+++ b/myapp/priv/rebar/boss_plugin.erl
@@ -0,0 +1,155 @@
+%%%-------------------------------------------------------------------
+%%% @author Jose Luis Gordo Romero
+%%% @doc Chicago Boss rebar plugin
+%%% Manage compilation/configuration/scripts stuff the rebar way
+%%% @end
+%%%-------------------------------------------------------------------
+-module(boss_plugin).
+
+-export([boss/2,
+ pre_compile/2,
+ pre_eunit/2]).
+
+-define(BOSS_CONFIG, "boss.config").
+
+%% ====================================================================
+%% Public API
+%% ====================================================================
+
+%%--------------------------------------------------------------------
+%% @doc boss command
+%% @spec boss(_Config, _AppFile) -> ok | {error, Reason}
+%% Boss enabled rebar commands, usage:
+%% ./rebar boss c=command
+%% @end
+%%--------------------------------------------------------------------
+boss(RebarConf, AppFile) ->
+ case is_base_dir() of
+ true ->
+ {ok, BossConf} = init(RebarConf, AppFile),
+ Command = rebar_config:get_global(c, "help"),
+ case boss_rebar:run(Command, RebarConf, BossConf, AppFile) of
+ {error, command_not_found} ->
+ io:format("ERROR: boss command not found.~n"),
+ boss_rebar:help(),
+ halt(1);
+ {error, Reason} ->
+ io:format("ERROR: executing ~s task: ~s~n", [Command, Reason]),
+ halt(1);
+ ok -> ok
+ end;
+ false -> ok
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc initializes the rebar boss connector plugin
+%% @spec init(Config, AppFile) -> {ok, BossConf} | {error, Reason}
+%% Set's the ebin cb_apps and loads the connector
+%% @end
+%%--------------------------------------------------------------------
+init(_RebarConf, AppFile) ->
+ %% Compile and load the boss_rebar code, this can't be compiled
+ %% as a normal boss lib without the rebar source dep
+ %% The load of ./rebar boss:
+ %% - Rebar itself searchs in rebar.config for {plugin_dir, ["priv/rebar"]}.
+ %% - Rebar itself compile this plugin and adds it to the execution chain
+ %% - This plugin compiles and loads the boss_rebar code in ["cb/priv/rebar"],
+ %% so we can extend/bugfix/tweak the framework without the need of manually
+ %% recopy code to user apps
+ BossPath = case boss_config_value(boss, path) of
+ {error, _} ->
+ io:format("FATAL: Failed to read boss=>path config in boss.config.~n"),
+ halt(1);
+ Val -> Val
+ end,
+ RebarErls = rebar_utils:find_files(filename:join([BossPath, "priv", "rebar"]), ".*\\.erl\$"),
+
+ rebar_log:log(debug, "Auto-loading boss rebar modules ~p~n", [RebarErls]),
+
+ lists:map(fun(F) ->
+ case compile:file(F, [binary]) of
+ error ->
+ io:format("FATAL: Failed compilation of ~s module~n", [F]),
+ halt(1);
+ {ok, M, Bin} ->
+ {module, _} = code:load_binary(M, F, Bin),
+ rebar_log:log(debug, "Loaded ~s~n", [M])
+ end
+ end, RebarErls),
+
+ BossConf = boss_config(),
+
+ %% add all cb_apps defined in boss.config to code path
+ %% including the deps ebin dirs
+ [code:add_path(CodePath) || CodePath <- boss_rebar:all_ebin_dirs(BossConf, AppFile)],
+
+ {ok, BossConf}.
+
+%%--------------------------------------------------------------------
+%% @doc pre_compile hook
+%% @spec pre_compile(_Config, AppFile) -> ok | {error, Reason}
+%% Pre compile hook, compile the boss way
+%% Compatibility hook, the normal ./rebar compile command works,
+%% but only calls the ./rebar boss c=compile and halts (default
+%% rebar task never hits)
+%% @end
+%%--------------------------------------------------------------------
+pre_compile(RebarConf, AppFile) ->
+ case is_base_dir() of
+ true ->
+ {ok, BossConf} = init(RebarConf, AppFile),
+ boss_rebar:run(compile, RebarConf, BossConf, AppFile),
+ halt(0);
+ false -> ok
+ end.
+
+%%--------------------------------------------------------------------
+%% @doc pre_eunit hook
+%% @spec pre_eunit(RebarConf, AppFile) -> ok | {error, Reason}
+%% Pre eunit hook, .eunit compilation the boss way
+%% Compatibility hook, the normal ./rebar eunit command works,
+%% but only calls the ./rebar boss c=test_eunit and halts
+%% (default rebar task never hits)
+%% @end
+%%--------------------------------------------------------------------
+pre_eunit(RebarConf, AppFile) ->
+ case is_base_dir() of
+ true ->
+ {ok, BossConf} = init(RebarConf, AppFile),
+ boss_rebar:run(test_eunit, RebarConf, BossConf, AppFile),
+ halt(0);
+ false -> ok
+ end.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+%% Checks if the current dir (rebar execution) is the base_dir
+%% Used to prevent run boss tasks in deps directory
+is_base_dir() ->
+ filename:absname(rebar_utils:get_cwd()) =:= rebar_config:get_global(base_dir, undefined).
+
+%% Gets the boss.config central configuration file
+boss_config() ->
+ {ok, BossConfig} = file:consult(?BOSS_CONFIG),
+ hd(BossConfig).
+
+%%--------------------------------------------------------------------
+%% @doc Get Boss config value app, key
+%% @spec boss_config_value(App, Key) -> Value | {error, Reason}
+%% Searchs in boss config for a given App and Key
+%% @end
+%%--------------------------------------------------------------------
+boss_config_value(App, Key) ->
+ case lists:keyfind(App, 1, boss_config()) of
+ false ->
+ {error, boss_config_app_not_found};
+ {App, AppConfig} ->
+ case lists:keyfind(Key, 1, AppConfig) of
+ false ->
+ {error, boss_config_app_setting_not_found};
+ {Key, KeyConfig} ->
+ KeyConfig
+ end
+ end.
diff --git a/myapp/priv/static/chicago-boss.png b/myapp/priv/static/chicago-boss.png
new file mode 100644
index 0000000..1403a88
Binary files /dev/null and b/myapp/priv/static/chicago-boss.png differ
diff --git a/myapp/priv/static/favicon.ico b/myapp/priv/static/favicon.ico
new file mode 100644
index 0000000..2dc01d3
Binary files /dev/null and b/myapp/priv/static/favicon.ico differ
diff --git a/myapp/rebar b/myapp/rebar
new file mode 100755
index 0000000..5328220
Binary files /dev/null and b/myapp/rebar differ
diff --git a/myapp/rebar.cmd b/myapp/rebar.cmd
new file mode 100644
index 0000000..6c7a1ca
--- /dev/null
+++ b/myapp/rebar.cmd
@@ -0,0 +1,4 @@
+@echo off
+setlocal
+set rebarscript=%~f0
+escript.exe "%rebarscript:.cmd=%" %*
diff --git a/myapp/rebar.config b/myapp/rebar.config
new file mode 100644
index 0000000..b9e40c8
--- /dev/null
+++ b/myapp/rebar.config
@@ -0,0 +1,3 @@
+{plugin_dir, ["priv/rebar"]}.
+{plugins, [boss_plugin]}.
+{eunit_compile_opts, [{src_dirs, ["src/test"]}]}.
\ No newline at end of file
diff --git a/myapp/src/mail/myapp_incoming_mail_controller.erl b/myapp/src/mail/myapp_incoming_mail_controller.erl
new file mode 100644
index 0000000..dff3df1
--- /dev/null
+++ b/myapp/src/mail/myapp_incoming_mail_controller.erl
@@ -0,0 +1,8 @@
+-module(myapp_incoming_mail_controller).
+-compile(export_all).
+
+authorize_(User, DomainName, IPAddress) ->
+ true.
+
+% post(FromAddress, Message) ->
+% ok.
diff --git a/myapp/src/mail/myapp_outgoing_mail_controller.erl b/myapp/src/mail/myapp_outgoing_mail_controller.erl
new file mode 100644
index 0000000..3ac04c5
--- /dev/null
+++ b/myapp/src/mail/myapp_outgoing_mail_controller.erl
@@ -0,0 +1,12 @@
+-module(myapp_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}]}.
diff --git a/myapp/src/myapp.app.src b/myapp/src/myapp.app.src
new file mode 100644
index 0000000..2a31cb3
--- /dev/null
+++ b/myapp/src/myapp.app.src
@@ -0,0 +1,8 @@
+{application, myapp, [
+ {description, "My Awesome Web Framework"},
+ {vsn, "0.0.1"},
+ {modules, []},
+ {registered, []},
+ {applications, [kernel, stdlib, crypto, boss]},
+ {env, []}
+ ]}.
diff --git a/myapp/src/view/lib/README b/myapp/src/view/lib/README
new file mode 100644
index 0000000..c75b105
--- /dev/null
+++ b/myapp/src/view/lib/README
@@ -0,0 +1,22 @@
+This directory contains:
+
+* tag_html/ - template files which are compiled to tags. If you have a file
+called "foo.html" and then call {% foo bar=1 %} from another template, the
+contents of "foo.html" will be evaluated with the "bar" variable set to 1.
+
+* tag_modules/ - Erlang modules that export functions to implement tags. If
+a module in this directory exports foo/1, then {% foo bar=1 %} will call
+
+ Module:foo([{bar, 1}])
+
+* filter_modules/ - Erlang modules that export functions to implement filters.
+If a module in this directory exports foo/1, then {% "Example"|foo %} will call
+
+ Module:foo(<<"Example">>)
+
+If module in this directory exports foo/2, then {% "Example"|foo:42 %} will call
+
+ Module:foo(<<"Example">>, 42)
+
+You can specify external tag and filter modules in the configuration via the
+template_tag_modules and template_filter_modules options.
diff --git a/myapp/src/view/lib/filter_modules/myapp_custom_filters.erl b/myapp/src/view/lib/filter_modules/myapp_custom_filters.erl
new file mode 100644
index 0000000..c5a2e77
--- /dev/null
+++ b/myapp/src/view/lib/filter_modules/myapp_custom_filters.erl
@@ -0,0 +1,9 @@
+-module(myapp_custom_filters).
+-compile(export_all).
+
+% put custom filters in here, e.g.
+%
+% my_reverse(Value) ->
+% lists:reverse(binary_to_list(Value)).
+%
+% "foo"|my_reverse => "foo"
diff --git a/myapp/src/view/lib/tag_modules/myapp_custom_tags.erl b/myapp/src/view/lib/tag_modules/myapp_custom_tags.erl
new file mode 100644
index 0000000..eab5fed
--- /dev/null
+++ b/myapp/src/view/lib/tag_modules/myapp_custom_tags.erl
@@ -0,0 +1,11 @@
+-module(myapp_custom_tags).
+-compile(export_all).
+
+% put custom tags in here, e.g.
+%
+% reverse(Variables, Options) ->
+% lists:reverse(binary_to_list(proplists:get_value(string, Variables))).
+%
+% {% reverse string="hello" %} => "olleh"
+%
+% Variables are the passed-in vars in your template
diff --git a/myapp/start-server.bat b/myapp/start-server.bat
new file mode 100644
index 0000000..2d5b5fb
--- /dev/null
+++ b/myapp/start-server.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+FOR /F "tokens=*" %%i in ('"rebar.cmd boss c=start_dev_cmd ^| findstr werl"') do set myvar=%%i
+START "Erlang Window" %myvar%
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..44b2c9b
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+cd ChicagoBoss
+make
+cd ../myapp
+./init.sh start-standalone
+while true; do
+ sleep 10
+done
+