use std::fs;
use crate::scaffold::{
add_to_controllers, add_to_models, add_to_tests, check_app, refuse_existing, write,
};
const CREATE_USERS: &str = include_str!("../templates/auth/create_users.rs");
const CREATE_SESSIONS: &str = include_str!("../templates/auth/create_sessions.rs");
const USER: &str = include_str!("../templates/auth/user.glacier");
const SESSION: &str = include_str!("../templates/auth/session.glacier");
const USER_FACTORY: &str = include_str!("../templates/auth/user_factory.glacier");
const USER_TEST: &str = include_str!("../templates/auth/user.test.glacier");
struct Flavour {
authentication: &'static str,
sessions: &'static str,
registrations: &'static str,
sessions_test: &'static str,
registrations_test: &'static str,
}
const BROWSER: Flavour = Flavour {
authentication: include_str!("../templates/auth/authentication.rs"),
sessions: include_str!("../templates/auth/sessions_controller.glacier"),
registrations: include_str!("../templates/auth/registrations_controller.glacier"),
sessions_test: include_str!("../templates/auth/sessions_controller.test.glacier"),
registrations_test: include_str!("../templates/auth/registrations_controller.test.glacier"),
};
const API: Flavour = Flavour {
authentication: include_str!("../templates/auth/authentication_api.rs"),
sessions: include_str!("../templates/auth/sessions_controller_api.glacier"),
registrations: include_str!("../templates/auth/registrations_controller_api.glacier"),
sessions_test: include_str!("../templates/auth/sessions_controller_api.test.glacier"),
registrations_test: include_str!("../templates/auth/registrations_controller_api.test.glacier"),
};
const SESSIONS_NEW: &str = include_str!("../templates/auth/sessions_new.html");
const REGISTRATIONS_NEW: &str = include_str!("../templates/auth/registrations_new.html");
pub fn authentication(api: bool) -> Result<(), String> {
check_app()?;
refuse_existing(&[
"src/models/user.glacier",
"src/models/user.rs",
"src/models/session.glacier",
"src/models/session.rs",
"src/authentication.rs",
"src/controllers/sessions_controller.glacier",
"src/controllers/registrations_controller.glacier",
])?;
let api = api || crate::scaffold::api_app();
let flavour = if api { &API } else { &BROWSER };
crate::generate::migration_with("create_users", CREATE_USERS)?;
crate::generate::migration_with("create_sessions", CREATE_SESSIONS)?;
write("src/models/user.glacier", USER)?;
write("src/models/session.glacier", SESSION)?;
add_to_models("session")?;
add_to_models("user")?;
write("src/authentication.rs", flavour.authentication)?;
write(
"src/controllers/sessions_controller.glacier",
flavour.sessions,
)?;
write(
"src/controllers/registrations_controller.glacier",
flavour.registrations,
)?;
add_to_controllers("registrations")?;
add_to_controllers("sessions")?;
if !api {
write("src/views/sessions/new.html", SESSIONS_NEW)?;
write("src/views/registrations/new.html", REGISTRATIONS_NEW)?;
}
write("tests/factories/user.glacier", USER_FACTORY)?;
write("tests/models/user.test.glacier", USER_TEST)?;
write(
"tests/controllers/sessions_controller.test.glacier",
flavour.sessions_test,
)?;
write(
"tests/controllers/registrations_controller.test.glacier",
flavour.registrations_test,
)?;
add_to_tests(&[
"controllers/registrations_controller",
"controllers/sessions_controller",
"factories/user",
"models/user",
])?;
register_module()?;
add_routes(api)?;
let protect = if api { "api" } else { "browser" };
println!(
"\nNext: glacier db migrate. Routes for signed-in users go in a scope\n\
through the `authenticated` pipeline:\n\n \
scope \"/\" through {protect}, authenticated {{\n \
get \"/account\" => Accounts::show, as account;\n }}\n\n\
and `current_user(&ctx)` (alias Authentication.current_user) is who's signed in."
);
Ok(())
}
fn register_module() -> Result<(), String> {
let path = "src/lib.rs";
let lib = fs::read_to_string(path).map_err(|e| format!("reading {path}: {e}"))?;
let mut updated = lib.clone();
if !updated.contains("pub mod authentication;") {
updated = match updated.find("pub mod controllers;") {
Some(at) => format!(
"{}pub mod authentication;\n{}",
&updated[..at],
&updated[at..]
),
None => format!("pub mod authentication;\n{updated}"),
};
}
let middleware = " // Who's signed in, for every request.\n \
.middleware(authentication::Authentication)\n";
if !updated.contains("authentication::Authentication") {
let anchor = " .middleware(Logger)\n";
match updated.find(anchor) {
Some(at) => updated.insert_str(at + anchor.len(), middleware),
None => println!(
"\nRegister the middleware on your App in {path}:\n\n \
.middleware(authentication::Authentication)"
),
}
}
if updated != lib {
fs::write(path, updated).map_err(|e| format!("writing {path}: {e}"))?;
println!(" update {path}");
}
Ok(())
}
fn add_routes(api: bool) -> Result<(), String> {
let path = "src/routes.rs";
let routes = fs::read_to_string(path).map_err(|e| format!("reading {path}: {e}"))?;
let mut updated = routes.clone();
let import = "use crate::authentication::RequireAuthentication;\n";
if !updated.contains(import) {
let anchor = "use crate::controllers::*;\n";
updated = match updated.find(anchor) {
Some(at) => format!("{}{import}{}", &updated[..at], &updated[at..]),
None => format!("{import}{updated}"),
};
}
let mut pipelines = String::new();
if api && !updated.contains("pipeline api ") {
pipelines.push_str(" // JSON APIs: errors as JSON, no CSRF (clients send tokens).\n");
pipelines.push_str(" pipeline api = Pipeline::api();\n");
}
pipelines.push_str(" // Only for signed-in users; see src/authentication.rs.\n");
pipelines
.push_str(" pipeline authenticated = Pipeline::new().with(RequireAuthentication);\n");
let scope = if api {
"\n scope \"/\" through api {\n \
post \"/session\" => Sessions::create, as session;\n \
delete \"/session\" => Sessions::destroy;\n \
post \"/registration\" => Registrations::create, as registration;\n }\n"
} else {
"\n scope \"/\" through browser {\n \
get \"/session/new\" => Sessions::new, as new_session;\n \
post \"/session\" => Sessions::create, as session;\n \
delete \"/session\" => Sessions::destroy;\n \
get \"/registration/new\" => Registrations::new, as new_registration;\n \
post \"/registration\" => Registrations::create, as registration;\n }\n"
};
let pipelines_at = updated
.rfind(" pipeline ")
.and_then(|at| updated[at..].find('\n').map(|end| at + end + 1));
let close = updated.rfind("\n}").map(|at| at + 1);
match (pipelines_at, close) {
(Some(pipelines_at), Some(close)) if pipelines_at < close => {
updated.insert_str(close, scope);
updated.insert_str(pipelines_at, &pipelines);
}
_ => {
println!("\nAdd these to the routes! table in {path}:\n\n{pipelines}{scope}");
}
}
if updated != routes {
fs::write(path, updated).map_err(|e| format!("writing {path}: {e}"))?;
println!(" update {path}");
}
Ok(())
}