//! By convention, root.zig is the root source file when making a package.
const std = @import("std");
const httpz = @import("httpz");
const Io = std.Io;
pub const sqlSchemaWriter =
\\CREATE TABLE IF NOT EXISTS writer(
\\ username TEXT PRIMARY KEY,
\\ password TEXT NOT NULL
\\);
;
pub const sqlSchemaSession =
\\CREATE TABLE IF NOT EXISTS session(
\\ token TEXT PRIMARY KEY,
\\ expires INTEGER NOT NULL,
\\ writer_username TEXT NOT NULL,
\\ FOREIGN KEY(writer_username) REFERENCES writer(username) ON DELETE CASCADE ON UPDATE CASCADE,
\\ UNIQUE(writer_username, token)
\\);
;
// basic hash: empty means there is none. server will store as hash<u+:+p>
// views_left: at query time, 0 means do not delete. if == 1, after fetching, will be deleted. if > 1, will be fetched
// delete_at: 0 means do not delete
pub const sqlSchemaPad =
\\CREATE TABLE IF NOT EXISTS pad(
\\ slug TEXT NOT NULL UNIQUE PRIMARY KEY CHECK(slug != '' AND slug != 'login' AND slug != 'logout' AND slug != 'signup'),
\\ content TEXT NOT NULL,
\\ basic_hash TEXT NOT NULL DEFAULT '',
\\ views_left INTEGER NOT NULL DEFAULT 0 CHECK (views_left >= 0),
\\ delete_at INTEGER NOT NULL DEFAULT 0 CHECK (delete_at >= 0),
\\ writer_username TEXT NOT NULL,
\\ FOREIGN KEY(writer_username) REFERENCES writer(username) ON DELETE CASCADE ON UPDATE CASCADE
\\);
;
pub fn h400Login(comptime val: []const u8) []const u8 {
const title: []const u8 = "Error " ++ val;
return htmlUtf8ViewportStyle ++
\\<meta name="description" content="
++ title ++
\\">
\\<title>
++ title ++
\\</title>
\\</head><body>
\\<h1>
++ title ++
\\</h1>
\\<p>actions:</p>
\\<ul>
\\<li><a href="/login">Log in</a></li>
\\<li><a href="/signup">Sign up</a></li>
\\</ul>
\\</body>
\\</html>
;
}
const htmlUtf8ViewportStyle =
\\<!doctype html><html lang="en"><head>
\\<meta charset="UTF-8">
\\<meta name="viewport" content="width=device-width, initial-scale=1">
\\<style>body{max-width:800px;margin:0 auto;padding:8px;} p,ul{font-size:1.5em;} @media(prefers-color-scheme:dark){body{color:white;background:black;}a{color:#6d6dff}a:visited:{color:#c573ff;}}</style>
;
const hasSessionNav =
\\ <nav>
\\ <details>
\\ <summary>Navigate</summary>
\\ <a href="/logout">Log out</a>
\\ <a href="/a">Pads</a>
\\ <a href="/newpad">New pad</a>
\\ <a href="/deleteaccount">Delete account</a>
\\ </details>
\\ </nav>
;
const noSessionNav =
\\ <nav>
\\ <details>
\\ <summary>Navigate</summary>
\\ <a href="/login">Log in</a>
\\ <a href="/signup">Sign up</a>
\\ </details>
\\ </nav>
;
const adminPageOpen =
htmlUtf8ViewportStyle ++
\\<meta name="description" content="Login - pad">
\\<title>Admin page</title>
\\</head><body>
++ hasSessionNav ++
\\<h1>Admin page</h1>
\\<div>
;
const adminPageClose =
\\</div>
\\</body></html>
;
pub const hNewPadPage =
htmlUtf8ViewportStyle ++
\\<meta name="description" content="New pad">
\\<title>New pad</title>
\\</head><body>
++ hasSessionNav ++
\\<h1>New pad</h1>
\\ <form action="newpad" method="post">
++ iptSlug ++
iptContent ++
iptPadUser ++
iptPadPass ++
iptViews ++
iptDeleteAt ++
iptSubmit("Create new pad") ++
formClose;
pub const Pad = struct {
slug: []const u8,
content: []const u8,
basic_hash: []const u8,
views_left: usize,
delete_at: usize,
writer_username: []const u8,
};
pub fn hAdminPage(alloc: std.mem.Allocator, user_pads: []Pad) ![]const u8 {
var list_pads: std.ArrayList([]const u8) = .empty;
for (user_pads) |ppad| {
std.debug.print("name: {s}\n", .{ppad.slug});
try list_pads.append(alloc, try std.fmt.allocPrint(alloc, "<div>{s} <form style=\"display:inline\" method=\"post\" action=\"/a/{s}/delete\"><input type=\"submit\" name=\"submit\" value=\"del\"></form></div>", .{ ppad.slug, ppad.slug }));
}
return try std.fmt.allocPrint(alloc, "{s}{s}{s}", .{ adminPageOpen, try std.mem.join(alloc, "", list_pads.items), adminPageClose });
}
const ErrForm = error{
Missing,
Empty,
};
pub fn formOrBad(req: *httpz.Request, res: *httpz.Response, form_name: []const u8, empty_ok: bool) ![]const u8 {
const fd = req.formData() catch |e| {
res.status = 400;
res.body = try std.fmt.allocPrint(res.arena, "bad form value for {s}", .{form_name});
return e;
};
const val = fd.get(form_name);
if (val == null) {
res.status = 400;
res.body = try std.fmt.allocPrint(res.arena, "bad form value for {s}", .{form_name});
return ErrForm.Missing;
}
if (!empty_ok) {
if (std.mem.eql(u8, val.?, "")) {
return ErrForm.Empty;
}
}
return val.?;
}
const iptUser = "<p><label>Username: <input required name=username></label></p>";
const iptPw = "<p><label>Password: <input required name=password type=\"password\"></label></p>";
const iptCode = "<p><label>Code: <input required name=code type=\"password\"></label></p>";
const iptSlug = "<p><label>Slug: <input required name=slug></label></p>";
const iptContent = "<p><label>Content: <textarea rows=8 required name=content></textarea></label></p>";
const iptPadUser = "<p><label>Username (optional): <input name=username></label></p>";
const iptPadPass = "<p><label>Password (optional): <input name=password type=\"password\"></label></p>";
const iptViews = "<p><label>Max views (0 for unlimited): <input name=views_left min=0 type=number value=0></label></p>";
const iptDeleteAt = "<p><label>Unix timestamp to delete at (0 for never): <input min=0 name=delete_at type=number value=0></label></p>";
const formClose =
\\ </form>
\\ </body></html>
;
pub const loginForm =
htmlUtf8ViewportStyle ++
metaTitle("Login") ++
\\ </head><body>
++ noSessionNav ++
\\ <h1>Login page</h1>
\\ <form action="login" method="post">
++ iptUser ++
iptPw ++
iptSubmit("Log in") ++
formClose;
pub const signupFormBase = htmlUtf8ViewportStyle ++
metaTitle("Signup") ++
\\ </head><body>
++ noSessionNav ++
\\ <h1>Signup page</h1>
\\ <form action="signup" method="post">
++ iptUser ++
iptPw;
pub const signupForm = signupFormBase ++
iptSubmit("Sign up") ++
formClose;
pub const signupFormCode = signupFormBase ++
iptCode ++
iptSubmit("Sign up") ++
formClose;
pub const deleteAccountForm = simpleFormOpen("Delete account", "deleteaccount") ++
"<p>Are you sure you want to delete your account? This will also delete all associated pads.</p>" ++
iptSubmit("Delete Account") ++
formClose;
fn iptSubmit(comptime val: []const u8) []const u8 {
return "<p><input type=submit name=submit value=\"" ++ val ++ "\"></p>";
}
fn metaTitle(comptime str: []const u8) []const u8 {
return "<meta name=\"description\" content=\"" ++ str ++ "\">\n" ++ "<title>" ++ str ++ "</title>";
}
fn simpleFormOpen(comptime title: []const u8, comptime action: []const u8) []const u8 {
return htmlUtf8ViewportStyle ++
metaTitle("Signup") ++
\\</head><body>
++ hasSessionNav ++
\\<h1>
++ title ++
\\</h1>
\\<form action="
++ action ++
\\" method="post">
;
}