add edit blog feature

This commit is contained in:
yuzu 2025-05-09 13:26:53 -05:00
parent e31a7fe09e
commit 7c294a5ecb
4 changed files with 145 additions and 35 deletions

View File

@ -223,6 +223,66 @@ pub fn delete(id: []const u8, request: *jetzig.Request) !jetzig.View {
} }
} }
// blogs/:id/edit
pub fn edit(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);
};
const session_query = jetzig.database.Query(.Session)
.findBy(.{ .session_id = session.value });
_ = request.repo.execute(session_query) catch {
return request.fail(.not_found);
};
switch (request.method) {
.GET => {
// get blog
const blog_query = jetzig.database.Query(.Blog)
.find(try std.fmt.parseInt(i32, id, 10));
const blog = request.repo.execute(blog_query) catch {
return request.fail(.not_found);
};
const root = try request.data(.object);
try root.put("allowed", true);
try root.put("blog", blog);
return request.render(.ok);
},
.POST => {
// make two queries for now
const params = try request.params();
const blog_query_original = jetzig.database.Query(.Blog)
.find(try std.fmt.parseInt(i32, id, 10));
if (try request.repo.execute(blog_query_original)) |blog| {
const title = params.getT(.string, "title") orelse blog.title;
const content = params.getT(.string, "content") orelse blog.content;
const preview = params.getT(.string, "preview") orelse blog.blob;
// query the blogpost first
const blog_query = jetzig.database.Query(.Blog)
.update(.{ .title = title, .blob = preview, .content = content })
.where(.{ .id = try std.fmt.parseInt(i32, id, 10) });
// update the blogpost
try request.repo.execute(blog_query);
return request.redirect("/blogs", .moved_permanently);
} else {
return request.fail(.not_found);
}
},
else => return request.fail(.not_found),
}
}
test "index" { test "index" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes")); var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit(); defer app.deinit();

View File

@ -0,0 +1,46 @@
<div class="max-w-2xl mx-auto mt-10 p-6 bg-white border border-win7-border shadow-md rounded-2xl font-sans text-win7-text">
<form action="/blogs/{{.blog.id}}/edit" method="POST" class="space-y-6">
<div>
<label for="title" class="block text-sm font-medium mb-1">Title</label>
<input
type="text"
name="title"
id="title"
value="{{.blog.title}}"
required
class="mt-1 w-full px-4 py-2 bg-white border border-win7-border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
/>
</div>
<div>
<label for="content" class="block text-sm font-medium mb-1">Content</label>
<textarea
name="content"
id="content"
rows="8"
required
class="mt-1 w-full px-4 py-2 bg-white border border-win7-border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
>{{.blog.content}}</textarea>
</div>
<div>
<label for="preview" class="block text-sm font-medium mb-1">Preview</label>
<textarea
name="preview"
id="preview"
rows="3"
class="mt-1 w-full px-4 py-2 bg-white border border-win7-border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
>{{.blog.blob}}</textarea>
</div>
<div>
<input
type="submit"
value="Save Changes"
class="px-4 py-2 bg-win7-link text-white font-semibold rounded-md shadow hover:bg-win7-hover transition"
/>
</div>
</form>
</div>

View File

@ -3,25 +3,27 @@
<div class="max-w-3xl mx-auto mt-10 space-y-6 font-sans"> <div class="max-w-3xl mx-auto mt-10 space-y-6 font-sans">
@for (.blogs) |blog| { @for (.blogs) |blog| {
<div class="p4 bg-win7-bg border border-win7-border rounded-xl px-5 py-4 shadow-sm hover:shadow-md transition duration-150"> <div class="p4 bg-win7-bg border border-win7-border rounded-xl px-5 py-4 shadow-sm hover:shadow-md transition duration-150">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<a href="/blogs/{{blog.id}}" class="text-xl font-semibold text-win7-link hover:underline transition"> <a href="/blogs/{{blog.id}}" class="text-xl font-semibold text-win7-link hover:underline transition">{{blog.title}}</a>
{{blog.title}}
</a>
@if ($.allowed) @if ($.allowed)
<button <div class="flex items-center gap-4">
id="delete-post-{{blog.id}}" <a href="/blogs/{{blog.id}}/edit"
class="text-sm text-win7-red hover:text-red-800 transition" class="text-sm text-win7-link hover:underline transition"
> >
Delete Edit
</button> </a>
@end <button
</div> id="delete-post-{{blog.id}}"
class="text-sm text-win7-red hover:text-red-800 transition"
<p class="text-xs text-[#4a5c75] mt-2"> >
{{zmpl.fmt.datetime(blog.get("created_at"), "%Y-%m-%d %H:%M")}} Delete
</p> </button>
</div> </div>
@end
</div>
<p class="text-xs text-[#4a5c75] mt-2">{{zmpl.fmt.datetime(blog.get("created_at"), "%Y-%m-%d %H:%M")}}</p>
</div>
} }
@if ($.allowed) @if ($.allowed)
@ -35,22 +37,22 @@
</div> </div>
@if ($.allowed) @if ($.allowed)
<script> <script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('[id^="delete-post-"]').forEach(button => { document.querySelectorAll('[id^="delete-post-"]').forEach(button => {
button.addEventListener("click", () => { button.addEventListener("click", () => {
const id = button.id.replace("delete-post-", ""); const id = button.id.replace("delete-post-", "");
fetch(`/blogs/${id}`, { fetch(`/blogs/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
}) })
.then(() => window.location.reload()) .then(() => window.location.reload())
.catch(err => console.error("Failed to delete post:", err)); .catch(err => console.error("Failed to delete post:", err));
}); });
}); });
}); });
</script> </script>
@end @end

View File

@ -105,6 +105,8 @@ pub const jetzig_options = struct {
pub fn init(app: *jetzig.App) !void { pub fn init(app: *jetzig.App) !void {
app.route(.GET, "/rss.xml", @import("app/views/rss.zig"), .index); app.route(.GET, "/rss.xml", @import("app/views/rss.zig"), .index);
app.route(.GET, "/blogs/:id/edit" , @import("app/views/blogs.zig"), .edit);
app.route(.POST, "/blogs/:id/edit" , @import("app/views/blogs.zig"), .edit);
} }
pub fn main() !void { pub fn main() !void {