const std = @import("std"); const jetzig = @import("jetzig"); const Zmd = @import("zmd").Zmd; const tokens = @import("zmd").tokens; const Node = @import("zmd").Node; pub const layout = "panel"; /// Default fragments. Pass this to `Zmd.toHtml` or provide your own. /// Formatters can be functions receiving an allocator, the current node, and the rendered /// content, or a 2-element tuple containing the open and close for each node. pub const Fragments = struct { pub fn root(allocator: std.mem.Allocator, node: Node) ![]const u8 { return try std.fmt.allocPrint(allocator, "{s}", .{node.content}); } pub fn block(allocator: std.mem.Allocator, node: Node) ![]const u8 { const style = "font-family: Monospace;"; return if (node.meta) |meta| std.fmt.allocPrint(allocator, \\
{s}
, .{ meta, style, node.content })
else
std.fmt.allocPrint(allocator,
\\{s}
, .{ style, node.content });
}
pub fn link(allocator: std.mem.Allocator, node: Node) ![]const u8 {
return std.fmt.allocPrint(allocator,
\\{s}
, .{ node.href.?, node.title.? });
}
pub fn image(allocator: std.mem.Allocator, node: Node) ![]const u8 {
return std.fmt.allocPrint(allocator,
\\", "
\n" }; pub const default = .{ "", "" }; }; /// Escape HTML entities. pub fn escape(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { const replacements = .{ .{ "&", "&" }, .{ "<", "<" }, .{ ">", ">" }, }; var output = input; inline for (replacements) |replacement| { output = try std.mem.replaceOwned(u8, allocator, output, replacement[0], replacement[1]); } return output; } pub fn index(request: *jetzig.Request) !jetzig.View { const query = jetzig.database.Query(.Blog).orderBy(.{.created_at = .desc}); const blogs = try request.repo.all(query); var root = try request.data(.object); try root.put("blogs", blogs); const cookies = try request.cookies(); const allowed = blk: { const session = cookies.get("session") orelse break :blk false; const session_query = jetzig.database.Query(.Session) .findBy(.{ .session_id = session.value }); _ = request.repo.execute(session_query) catch break :blk false; break :blk true; }; try root.put("allowed", allowed); return request.render(.ok); } pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View { const query = jetzig.database.Query(.Blog).find(id); const blog = try request.repo.execute(query) orelse return request.fail(.not_found); const Blog = struct { id: i32, title: []const u8, content: ?[]const u8, created_at: jetzig.jetquery.DateTime, updated_at: jetzig.jetquery.DateTime, }; var zmd = Zmd.init(request.allocator); defer zmd.deinit(); try zmd.parse(blog.content orelse { return request.fail(.unprocessable_entity); }); const blob = try zmd.toHtml(Fragments); const eb = Blog{ .id = blog.id, .title = blog.title, .content = blob, .created_at = blog.created_at, .updated_at = blog.updated_at, }; var root = try request.data(.object); try root.put("blog", eb); const cookies = try request.cookies(); const allowed = blk: { const session = cookies.get("session") orelse break :blk false; const session_query = jetzig.database.Query(.Session) .findBy(.{ .session_id = session.value }); _ = request.repo.execute(session_query) catch break :blk false; break :blk true; }; try root.put("allowed", allowed); try request.process(); return request.render(.ok); } pub fn new(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { const cookies = try request.cookies(); const allowed = blk: { const session = cookies.get("session") orelse break :blk false; const session_query = jetzig.database.Query(.Session) .findBy(.{ .session_id = session.value }); _ = request.repo.execute(session_query) catch break :blk false; break :blk true; }; const root = try data.object(); try root.put("allowed", allowed); return request.render(.ok); } pub fn post(request: *jetzig.Request) !jetzig.View { // check "session" cookie for authentication const cookies = try request.cookies(); const session = cookies.get("session") orelse { return request.fail(.not_found); }; // check if session is valid const query = jetzig.database.Query(.Session) .findBy(.{ .session_id = session.value }); if (try request.repo.execute(query)) |_| { const params = try request.params(); const title = params.getT(.string, "title") orelse { return request.fail(.unprocessable_entity); }; const content = params.getT(.string, "content") orelse { return request.fail(.unprocessable_entity); }; const preview = params.getT(.string, "preview") orelse { return request.fail(.unprocessable_entity); }; try request.repo.insert(.Blog, .{ .title = title, .blob = preview, .content = content, }); return request.redirect("/blogs", .moved_permanently); } else { return request.fail(.not_found); } } pub fn delete(id: []const u8, request: *jetzig.Request) !jetzig.View { // check "session" cookie for authentication const cookies = try request.cookies(); const session = cookies.get("session") orelse { return request.fail(.not_found); }; // check if session is valid const query = jetzig.database.Query(.Session) .findBy(.{ .session_id = session.value }); if (try request.repo.execute(query)) |_| { const delete_query = jetzig.database.Query(.Blog) .find(try std.fmt.parseInt(i32, id, 10)); if (try request.repo.execute(delete_query)) |blog_post| { try request.repo.delete(blog_post); } return request.redirect("/blogs", .moved_permanently); } else { return request.fail(.not_found); } } test "index" { var app = try jetzig.testing.app(std.testing.allocator, @import("routes")); defer app.deinit(); const response = try app.request(.GET, "/blogs", .{}); try response.expectStatus(.ok); } test "get" { var app = try jetzig.testing.app(std.testing.allocator, @import("routes")); defer app.deinit(); const response = try app.request(.GET, "/blogs/example-id", .{}); try response.expectStatus(.ok); } test "new" { var app = try jetzig.testing.app(std.testing.allocator, @import("routes")); defer app.deinit(); const response = try app.request(.GET, "/blogs/new", .{}); try response.expectStatus(.ok); } test "post" { var app = try jetzig.testing.app(std.testing.allocator, @import("routes")); defer app.deinit(); const response = try app.request(.POST, "/blogs", .{}); try response.expectStatus(.created); }