-module(rally@init).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/init.gleam").
-export([files/1, init_project/1]).
-export_type([scaffold_file/0]).
-type scaffold_file() :: {scaffold_file, binary(), binary()}.
-file("src/rally/init.gleam", 89).
-spec join(binary(), binary()) -> binary().
join(Root, Path) ->
case Root of
<<"."/utf8>> ->
Path;
_ ->
<<<<Root/binary, "/"/utf8>>/binary, Path/binary>>
end.
-file("src/rally/init.gleam", 96).
-spec set_executable(binary()) -> {ok, nil} | {error, binary()}.
set_executable(Path) ->
case rally_cli_ffi:find_executable(<<"chmod"/utf8>>) of
{some, Chmod} ->
case rally_cli_ffi:run_executable(Chmod, [<<"+x"/utf8>>, Path]) of
0 ->
{ok, nil};
_ ->
{error,
<<<<"Failed to mark "/utf8, Path/binary>>/binary,
" executable"/utf8>>}
end;
none ->
{ok, nil}
end.
-file("src/rally/init.gleam", 428).
-spec dev_script() -> binary().
dev_script() ->
<<"#!/usr/bin/env bash
set -euo pipefail
cd \"$(dirname \"$0\")/..\"
if [ -f \".env\" ]; then
set -a; . .env; set +a
fi
export APP_ENV=\"${APP_ENV:-dev}\"
echo \"==> Running rally codegen...\"
gleam run -m rally
echo \"==> Building client...\"
cd .generated_clients/public && gleam build --target javascript && cd ../..
echo \"==> Starting server...\"
gleam run -m app
"/utf8>>.
-file("src/rally/init.gleam", 418).
-spec server_context() -> binary().
server_context() ->
<<"// Scaffolded by rally: yours to customize.
import sqlight
pub type ServerContext {
ServerContext(db: sqlight.Connection)
}
"/utf8>>.
-file("src/rally/init.gleam", 401).
-spec shell_html() -> binary().
shell_html() ->
<<"<!-- Scaffolded by rally: yours to customize. -->
<!DOCTYPE html>
<html>
<head>
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>My App</title>
</head>
<body>
<div id=\"app\"></div>
<script type=\"module\" src=\"/client.js\"></script>
</body>
</html>
"/utf8>>.
-file("src/rally/init.gleam", 246).
-spec app_module() -> binary().
app_module() ->
<<"// Scaffolded by rally: yours to customize.
import gleam/bytes_tree
import gleam/erlang/process
import gleam/http.{Get, Post}
import gleam/http/request.{type Request, Request}
import gleam/http/response
import mist.{type Connection, type ResponseData}
import generated/public/http_handler as http_handler
import generated/public/router as router
import generated/public/ssr_handler as ssr_handler
import generated/public/ws_handler as ws_handler
import rally_runtime/db
import rally_runtime/env
import rally_runtime/session
import rally_runtime/system
import server_context.{ServerContext}
import simplifile
import sqlight
pub fn main() {
let db = start_db()
system.start(\"system.db\")
let server_context = ServerContext(db:)
let handler = fn(req: Request(Connection)) {
let Request(path: path, method: method, ..) = req
case path {
\"/ws\" -> {
let session_id = get_session_id(req)
let hostname = request_header(req, \"host\")
mist.websocket(
req,
ws_handler.handler,
fn(conn) {
ws_handler.on_init(
conn: conn,
server_context: server_context,
session_id: session_id,
hostname: hostname,
)
},
ws_handler.on_close,
)
}
\"/rpc\" -> handle_rpc(req, server_context)
\"/client.js\" -> serve_client_js()
_ -> {
case method {
Get -> {
let session_id = get_session_id(req)
let route = router.parse_route(request.to_uri(req))
let resp = ssr_handler.handle_request(route)
set_session_cookie_if_missing(req, resp, session_id)
}
_ ->
response.new(405)
|> response.set_body(mist.Bytes(bytes_tree.from_string(\"Not found\")))
}
}
}
}
let assert Ok(_) =
mist.new(handler)
|> mist.port(8080)
|> mist.start
process.sleep_forever()
}
fn handle_rpc(req: Request(Connection), server_context: ServerContext) {
case req.method {
Post -> {
let session_id = get_session_id(req)
case mist.read_body(req, max_body_limit: 16_000_000) {
Ok(Request(body: body, ..)) -> {
let resp =
http_handler.handle(
body: body,
server_context: server_context,
session_id: session_id,
)
set_session_cookie_if_missing(req, resp, session_id)
}
Error(_) ->
response.new(413)
|> response.set_body(
mist.Bytes(bytes_tree.from_string(\"Request body too large\")),
)
}
}
_ ->
response.new(405)
|> response.set_body(mist.Bytes(bytes_tree.from_string(\"Not found\")))
}
}
fn request_header(req: Request(Connection), name: String) -> String {
case request.get_header(req, name) {
Ok(value) -> value
Error(_) -> \"\"
}
}
fn get_session_id(req: Request(Connection)) -> String {
case request.get_header(req, \"cookie\") {
Ok(cookie) ->
case session.extract_session_id(cookie) {
Ok(id) -> id
Error(_) -> session.generate_id()
}
Error(_) -> session.generate_id()
}
}
fn set_session_cookie_if_missing(req, resp, session_id: String) {
case request.get_header(req, \"cookie\") {
Ok(cookie) ->
case session.extract_session_id(cookie) {
Ok(_) -> resp
Error(_) ->
response.set_header(
resp,
\"set-cookie\",
session.set_cookie_header(session_id:, secure: env.secure_cookies()),
)
}
Error(_) ->
response.set_header(
resp,
\"set-cookie\",
session.set_cookie_header(session_id:, secure: env.secure_cookies()),
)
}
}
fn serve_client_js() {
case simplifile.read(\".generated_clients/public/build/dev/javascript/client/generated/app.mjs\") {
Ok(js) ->
response.new(200)
|> response.set_header(\"content-type\", \"application/javascript\")
|> response.set_body(mist.Bytes(bytes_tree.from_string(js)))
Error(_) ->
response.new(404)
|> response.set_body(mist.Bytes(bytes_tree.from_string(\"Client JS not found\")))
}
}
fn start_db() -> sqlight.Connection {
let assert Ok(conn) = db.open(\"app.db\")
conn
}
"/utf8>>.
-file("src/rally/init.gleam", 236).
-spec layout_page() -> binary().
layout_page() ->
<<"// Scaffolded by rally: yours to customize.
import lustre/element.{type Element}
pub fn layout(content: Element(msg)) -> Element(msg) {
content
}
"/utf8>>.
-file("src/rally/init.gleam", 169).
-spec home_page() -> binary().
home_page() ->
<<"// Scaffolded by rally: yours to customize.
import gleam/string
import lustre/element.{type Element}
import lustre/element/html
import lustre/effect.{type Effect}
import lustre/event
import rally_runtime/effect as rally_effect
import server_context.{type ServerContext}
pub type Model {
Model(count: Int)
}
pub type Msg {
UserClickedIncrement
UserClickedDecrement
GotIncrement(Result(Int, Nil))
}
pub type ServerIncrement {
ServerIncrement
}
pub type ServerDecrement {
ServerDecrement
}
pub fn init() -> #(Model, Effect(Msg)) {
#(Model(count: 0), effect.none())
}
pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
case msg {
UserClickedIncrement ->
#(model, rally_effect.rpc(ServerIncrement, on_response: GotIncrement))
UserClickedDecrement ->
#(model, rally_effect.rpc(ServerDecrement, on_response: GotIncrement))
GotIncrement(Ok(n)) -> #(Model(count: model.count + n), effect.none())
GotIncrement(Error(_)) -> #(model, effect.none())
}
}
pub fn view(model: Model) -> Element(Msg) {
html.div([], [
html.button([event.on_click(UserClickedIncrement)], [html.text(\"+\")]),
html.text(string.inspect(model.count)),
html.button([event.on_click(UserClickedDecrement)], [html.text(\"-\")]),
])
}
pub fn server_increment(
msg _msg: ServerIncrement,
server_context _server_context: ServerContext,
) -> Result(Int, Nil) {
Ok(1)
}
pub fn server_decrement(
msg _msg: ServerDecrement,
server_context _server_context: ServerContext,
) -> Result(Int, Nil) {
Ok(-1)
}
"/utf8>>.
-file("src/rally/init.gleam", 130).
-spec gleam_toml(binary()) -> binary().
gleam_toml(Project_name) ->
<<<<"name = \""/utf8, Project_name/binary>>/binary,
"\"
version = \"0.1.0\"
target = \"erlang\"
[dependencies]
gleam_erlang = \">= 1.0.0 and < 2.0.0\"
gleam_http = \">= 4.0.0 and < 5.0.0\"
gleam_stdlib = \">= 0.60.0 and < 2.0.0\"
rally = \">= 1.0.0 and < 2.0.0\"
libero = \">= 6.0.0 and < 7.0.0\"
lustre = \">= 5.6.0 and < 7.0.0\"
marmot = \">= 1.3.0 and < 2.0.0\"
mist = \">= 6.0.0 and < 7.0.0\"
sqlight = \">= 1.0.0 and < 2.0.0\"
simplifile = \">= 2.0.0 and < 3.0.0\"
gleam_time = \">= 1.7.0 and < 2.0.0\"
[dev-dependencies]
gleeunit = \">= 1.0.0 and < 2.0.0\"
birdie = \">= 2.0.0 and < 3.0.0\"
glinter = \">= 2.16.0 and < 3.0.0\"
[tools.glinter]
stats = true
warnings_as_errors = true
exclude = [\"src/generated/\"]
[[tools.rally.clients]]
namespace = \"public\"
route_root = \"/\"
[tools.marmot]
database = \"app.db\"
sql_dir = \"src/sql\"
output = \"src/generated/sql\"
"/utf8>>.
-file("src/rally/init.gleam", 124).
-spec env_example() -> binary().
env_example() ->
<<"APP_ENV=dev
LOG_LEVEL=debug
"/utf8>>.
-file("src/rally/init.gleam", 114).
-spec gitignore() -> binary().
gitignore() ->
<<"build/
app.db
erl_crash.dump
*.bak
.DS_Store
.generated_clients/
"/utf8>>.
-file("src/rally/init.gleam", 19).
-spec files(binary()) -> list(scaffold_file()).
files(Project_name) ->
[{scaffold_file, <<".gitignore"/utf8>>, gitignore()},
{scaffold_file, <<".env.example"/utf8>>, env_example()},
{scaffold_file, <<"gleam.toml"/utf8>>, gleam_toml(Project_name)},
{scaffold_file, <<"src/public/pages/home_.gleam"/utf8>>, home_page()},
{scaffold_file, <<"src/public/pages/layout.gleam"/utf8>>, layout_page()},
{scaffold_file, <<"src/app.gleam"/utf8>>, app_module()},
{scaffold_file, <<"src/public/shell.html"/utf8>>, shell_html()},
{scaffold_file, <<"src/server_context.gleam"/utf8>>, server_context()},
{scaffold_file, <<"bin/dev"/utf8>>, dev_script()}].
-file("src/rally/init.gleam", 50).
-spec write_files(binary(), list(scaffold_file())) -> {ok, nil} |
{error, binary()}.
write_files(Root, Files) ->
_pipe = Files,
gleam@list:try_each(
_pipe,
fun(File) ->
Path = join(Root, erlang:element(2, File)),
_pipe@1 = simplifile:write(Path, erlang:element(3, File)),
gleam@result:map_error(
_pipe@1,
fun(E) ->
<<<<<<"Failed to write "/utf8, Path/binary>>/binary,
": "/utf8>>/binary,
(simplifile:describe_error(E))/binary>>
end
)
end
).
-file("src/rally/init.gleam", 33).
-spec create_dirs(binary()) -> {ok, nil} | {error, binary()}.
create_dirs(Root) ->
_pipe = [<<"src/public/pages"/utf8>>,
<<"src/sql"/utf8>>,
<<"src/generated/public"/utf8>>,
<<".generated_clients/public/src/generated"/utf8>>,
<<"bin"/utf8>>],
gleam@list:try_each(
_pipe,
fun(Dir) ->
Path = join(Root, Dir),
_pipe@1 = simplifile:create_directory_all(Path),
gleam@result:map_error(
_pipe@1,
fun(E) ->
<<<<<<"Failed to create "/utf8, Path/binary>>/binary,
": "/utf8>>/binary,
(simplifile:describe_error(E))/binary>>
end
)
end
).
-file("src/rally/init.gleam", 81).
-spec basename(binary()) -> binary().
basename(Path) ->
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
_pipe@2 = lists:reverse(_pipe@1),
_pipe@3 = gleam@list:first(_pipe@2),
gleam@result:unwrap(_pipe@3, <<"rally_app"/utf8>>).
-file("src/rally/init.gleam", 74).
-spec trim_trailing_slash(binary()) -> binary().
trim_trailing_slash(Path) ->
case gleam_stdlib:string_ends_with(Path, <<"/"/utf8>>) of
true ->
_pipe = gleam@string:drop_end(Path, 1),
trim_trailing_slash(_pipe);
false ->
Path
end.
-file("src/rally/init.gleam", 61).
-spec project_name(binary()) -> binary().
project_name(Root) ->
Path = case Root of
<<"."/utf8>> ->
_pipe = simplifile:current_directory(),
gleam@result:unwrap(_pipe, <<"rally_app"/utf8>>);
Other ->
Other
end,
_pipe@1 = Path,
_pipe@2 = trim_trailing_slash(_pipe@1),
_pipe@3 = basename(_pipe@2),
_pipe@4 = gleam@string:replace(_pipe@3, <<"-"/utf8>>, <<"_"/utf8>>),
string:lowercase(_pipe@4).
-file("src/rally/init.gleam", 11).
-spec init_project(binary()) -> {ok, nil} | {error, binary()}.
init_project(Root) ->
Name = project_name(Root),
gleam@result:'try'(
create_dirs(Root),
fun(_use0) ->
nil = _use0,
gleam@result:'try'(
write_files(Root, files(Name)),
fun(_use0@1) ->
nil = _use0@1,
set_executable(join(Root, <<"bin/dev"/utf8>>))
end
)
end
).