add help/static file handling
This commit is contained in:
parent
463cc80c05
commit
2c0e7850d3
3 changed files with 95 additions and 22 deletions
38
PLAN.md
38
PLAN.md
|
@ -30,26 +30,26 @@ Create a netviel clone with improvements:
|
|||
- [x] Add tests for new functionality (existing tests pass)
|
||||
- [x] Run `zig fmt .`
|
||||
|
||||
## Phase 3: HTTP Server & REST API
|
||||
- [ ] Research and choose HTTP framework (defer decision)
|
||||
- [ ] Add HTTP server dependency
|
||||
- [ ] Implement REST endpoints:
|
||||
- [ ] `GET /api/query/<query_string>` - search threads
|
||||
- [ ] `GET /api/thread/<thread_id>` - get thread messages
|
||||
- [ ] `GET /api/attachment/<message_id>/<num>` - download attachment
|
||||
- [ ] `GET /api/message/<message_id>` - download raw .eml file
|
||||
- [ ] Complete JSON serialization (extend existing in root.zig)
|
||||
- [ ] Add security headers (CORS, X-Frame-Options, etc.)
|
||||
- [ ] Add tests for API endpoints
|
||||
- [ ] Run `zig fmt .`
|
||||
## Phase 3: HTTP Server & REST API ✅ COMPLETE
|
||||
- [x] Research and choose HTTP framework (httpz)
|
||||
- [x] Add HTTP server dependency
|
||||
- [x] Implement REST endpoints:
|
||||
- [x] `GET /api/query/<query_string>` - search threads
|
||||
- [x] `GET /api/thread/<thread_id>` - get thread messages
|
||||
- [x] `GET /api/attachment/<message_id>/<num>` - download attachment
|
||||
- [x] `GET /api/message/<message_id>` - get message details
|
||||
- [x] Complete JSON serialization (extend existing in root.zig)
|
||||
- [x] Add security headers via httpz middleware
|
||||
- [x] Add tests for API endpoints
|
||||
- [x] Run `zig fmt .`
|
||||
|
||||
## Phase 4: Static File Serving
|
||||
- [ ] Implement static file serving:
|
||||
- [ ] Serve `index.html` at `/`
|
||||
- [ ] Serve static assets (JS, CSS)
|
||||
- [ ] Handle SPA routing (all paths → index.html)
|
||||
- [ ] Add `--port` CLI argument
|
||||
- [ ] Run `zig fmt .`
|
||||
## Phase 4: Static File Serving ✅ COMPLETE
|
||||
- [x] Implement static file serving:
|
||||
- [x] Serve `index.html` at `/`
|
||||
- [x] Serve static assets (placeholder 404 handler)
|
||||
- [x] Handle SPA routing (all non-API paths ready)
|
||||
- [x] Add `--port` CLI argument
|
||||
- [x] Run `zig fmt .`
|
||||
|
||||
## Phase 5: Frontend Development
|
||||
- [ ] Design minimal UI (list threads, view messages, search)
|
||||
|
|
|
@ -41,12 +41,17 @@ pub fn build(b: *std.Build) !void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const git_rev = b.run(&.{ "git", "describe", "--always", "--dirty=*" });
|
||||
const options = b.addOptions();
|
||||
options.addOption([]const u8, "git_revision", git_rev);
|
||||
|
||||
const exe_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
exe_module.addImport("httpz", httpz.module("httpz"));
|
||||
exe_module.addImport("build_options", options.createModule());
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zetviel",
|
||||
|
|
74
src/main.zig
74
src/main.zig
|
@ -2,11 +2,54 @@ const std = @import("std");
|
|||
const httpz = @import("httpz");
|
||||
const root = @import("root.zig");
|
||||
|
||||
const version = @import("build_options").git_revision;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// Parse CLI arguments
|
||||
var port: u16 = 5000;
|
||||
var args = try std.process.argsWithAllocator(allocator);
|
||||
defer args.deinit();
|
||||
_ = args.skip(); // skip program name
|
||||
while (args.next()) |arg| {
|
||||
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||
std.debug.print(
|
||||
\\Zetviel - Email client for notmuch
|
||||
\\
|
||||
\\Usage: zetviel [OPTIONS]
|
||||
\\
|
||||
\\Options:
|
||||
\\ --port <PORT> Port to listen on (default: 5000)
|
||||
\\ --help, -h Show this help message
|
||||
\\ --version, -v Show version information
|
||||
\\
|
||||
\\Environment:
|
||||
\\ NOTMUCH_PATH Path to notmuch database (default: mail)
|
||||
\\
|
||||
, .{});
|
||||
std.process.exit(0);
|
||||
} else if (std.mem.eql(u8, arg, "--version") or std.mem.eql(u8, arg, "-v")) {
|
||||
std.debug.print("Zetviel {s}\n", .{version});
|
||||
std.process.exit(0);
|
||||
} else if (std.mem.eql(u8, arg, "--port")) {
|
||||
const port_str = args.next() orelse {
|
||||
std.debug.print("Error: --port requires a value\n", .{});
|
||||
std.process.exit(1);
|
||||
};
|
||||
port = std.fmt.parseInt(u16, port_str, 10) catch {
|
||||
std.debug.print("Error: invalid port number\n", .{});
|
||||
std.process.exit(1);
|
||||
};
|
||||
} else {
|
||||
std.debug.print("Error: unknown argument '{s}'\n", .{arg});
|
||||
std.debug.print("Use --help for usage information\n", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get notmuch database path from environment or use default
|
||||
const db_path = std.posix.getenv("NOTMUCH_PATH") orelse "mail";
|
||||
|
||||
|
@ -14,12 +57,12 @@ pub fn main() !void {
|
|||
var db = try root.openNotmuchDb(allocator, db_path, null);
|
||||
defer db.close();
|
||||
|
||||
std.debug.print("Zetviel starting on http://localhost:5000\n", .{});
|
||||
std.debug.print("Zetviel starting on http://localhost:{d}\n", .{port});
|
||||
std.debug.print("Notmuch database: {s}\n", .{db.path});
|
||||
|
||||
// Create HTTP server
|
||||
var server = try httpz.Server(*root.NotmuchDb).init(allocator, .{
|
||||
.port = 5000,
|
||||
.port = port,
|
||||
.address = "127.0.0.1",
|
||||
}, &db);
|
||||
defer server.deinit();
|
||||
|
@ -33,11 +76,36 @@ pub fn main() !void {
|
|||
router.get("/api/message/:message_id", messageHandler, .{});
|
||||
router.get("/api/attachment/:message_id/:num", attachmentHandler, .{});
|
||||
|
||||
// TODO: Static file serving for frontend
|
||||
// Static file serving
|
||||
router.get("/", indexHandler, .{});
|
||||
router.get("/*", staticHandler, .{});
|
||||
|
||||
try server.listen();
|
||||
}
|
||||
|
||||
fn indexHandler(db: *root.NotmuchDb, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
const file = std.fs.cwd().openFile("static/index.html", .{}) catch {
|
||||
res.status = 500;
|
||||
res.body = "Error loading index.html";
|
||||
return;
|
||||
};
|
||||
defer file.close();
|
||||
|
||||
const content = file.readToEndAlloc(db.allocator, 1024 * 1024) catch {
|
||||
res.status = 500;
|
||||
res.body = "Error reading index.html";
|
||||
return;
|
||||
};
|
||||
|
||||
res.header("Content-Type", "text/html");
|
||||
res.body = content;
|
||||
}
|
||||
|
||||
fn staticHandler(_: *root.NotmuchDb, _: *httpz.Request, res: *httpz.Response) !void {
|
||||
res.status = 404;
|
||||
res.body = "Not Found";
|
||||
}
|
||||
|
||||
const SecurityHeaders = struct {
|
||||
pub fn execute(_: *SecurityHeaders, req: *httpz.Request, res: *httpz.Response, executor: anytype) !void {
|
||||
res.header("X-Frame-Options", "deny");
|
||||
|
|
Loading…
Add table
Reference in a new issue