//! 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"> ; }