const std = @import("std"); const assets = @import("assets.zig"); const config = @import("../../core/config.zig"); const entropy = @import("entropy.zig"); const http_api = @import("../api.zig"); const store = @import("../../storage/store.zig"); const http = std.http; pub fn serve(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const body = try @import("../../internal/web/icons.zig").decorate(allocator, try render(allocator)); const headers = [_]http.Header{ .{ .name = "content-type", .value = "text/html; charset=utf-8" }, .{ .name = "cache-control", .value = "no-store" }, .{ .name = "access-control-allow-origin", .value = "*" }, .{ .name = "access-control-allow-private-network", .value = "true" }, .{ .name = "connection", .value = "close" }, }; try http_api.respond(request, .ok, body, &headers); } pub fn serveFavicon(request: *http_api.Request) !void { const headers = [_]http.Header{ .{ .name = "content-type", .value = "image/svg+xml; charset=utf-8" }, .{ .name = "cache-control", .value = "public, max-age=86400" }, .{ .name = "access-control-allow-origin", .value = "*" }, .{ .name = "connection", .value = "close" }, }; try http_api.respond(request, .ok, assets.favicon, &headers); } pub fn serveOgImage(request: *http_api.Request) !void { const headers = [_]http.Header{ .{ .name = "content-type", .value = "image/png" }, .{ .name = "cache-control", .value = "public, max-age=86400" }, .{ .name = "access-control-allow-origin", .value = "*" }, .{ .name = "connection", .value = "close" }, }; try http_api.respond(request, .ok, assets.og_image, &headers); } fn render(allocator: std.mem.Allocator) ![]const u8 { const residents = try store.listResidents(allocator, 24); var accounts: std.Io.Writer.Allocating = .init(allocator); defer accounts.deinit(); if (residents.len == 0) { try accounts.writer.writeAll("
  • no residents yet.
  • "); } else { for (residents) |resident| { const handle = try escapeHtml(allocator, resident.handle); const did = try escapeHtml(allocator, resident.did); const avatar = try renderAvatar(allocator, resident); const status = if (resident.active) "active" else "paused"; try accounts.writer.print( \\
  • \\ \\ {s} \\ \\ {s} \\ {s} \\ \\ {s}{d} records \\ \\
  • , .{ handle, std.json.fmt(resident.handle, .{}), std.json.fmt(resident.did, .{}), avatar, handle, did, status, resident.record_count }); } } const collections = try store.listCollectionSummaries(allocator, 10); const activity = try store.listLandingActivity(allocator, 18); const signal = try entropy.fromState(allocator, residents, collections, activity); var collection_items: std.Io.Writer.Allocating = .init(allocator); defer collection_items.deinit(); if (collections.len == 0) { try collection_items.writer.writeAll("
  • no records yet.
  • "); } else { const max_count = collections[0].count; for (collections) |summary| { const collection = try escapeHtml(allocator, summary.collection); const width = if (max_count == 0) 0 else @max(@as(u64, 8), summary.count * 100 / max_count); try collection_items.writer.print( \\
  • \\ {s} \\ {d} \\ \\
  • , .{ collection, collection, summary.count, width }); } } const recent_records = try store.listLandingRecentRecords(allocator, 8); const pulse = try renderPulse(allocator, recent_records); const public_url = config.publicUrl(); const public_host = try escapeHtml(allocator, displayHost(public_url)); const public_url_html = try escapeHtml(allocator, public_url); const preview_image = try escapeHtml(allocator, try std.fmt.allocPrint(allocator, "{s}/og-image", .{public_url})); return std.fmt.allocPrint(allocator, @embedFile("../../internal/web/home.html"), .{ public_url_html, public_host, public_url_html, preview_image, public_host, preview_image, assets.css, signal.style, public_host, signal.html, pulse, accounts.written(), collection_items.written(), assets.script, }); } fn renderPulse(allocator: std.mem.Allocator, records: []const store.RecentRecord) ![]const u8 { if (records.len == 0) { return allocator.dupe(u8, \\
    \\
    repo signal
    waiting for first write.
    \\
    ); } const newest = records[0]; const collection = try escapeHtml(allocator, newest.collection); const rkey = try escapeHtml(allocator, newest.rkey); const cid = try escapeHtml(allocator, newest.cid); const did = try escapeHtml(allocator, newest.did); return std.fmt.allocPrint(allocator, \\
    \\
    \\
    latest indexed record
    \\ \\ {s}/{s} \\ seq {d} \\ \\
    {s}
    \\
    \\
    , .{ did, collection, rkey, collection, rkey, newest.seq, cid }); } fn renderAvatar(allocator: std.mem.Allocator, resident: store.Resident) ![]const u8 { const avatar_cid = try store.profileAvatarCid(allocator, resident.did) orelse { return std.fmt.allocPrint(allocator, "{c}", .{initialFor(resident.handle)}); }; const did = try escapeHtml(allocator, resident.did); const cid = try escapeHtml(allocator, avatar_cid); return std.fmt.allocPrint( allocator, "\"\"", .{ did, cid }, ); } fn initialFor(handle: []const u8) u8 { for (handle) |c| if (std.ascii.isAlphanumeric(c)) return std.ascii.toLower(c); return 'z'; } fn escapeHtml(allocator: std.mem.Allocator, text: []const u8) ![]const u8 { var out: std.Io.Writer.Allocating = .init(allocator); defer out.deinit(); for (text) |c| switch (c) { '&' => try out.writer.writeAll("&"), '<' => try out.writer.writeAll("<"), '>' => try out.writer.writeAll(">"), '"' => try out.writer.writeAll("""), '\'' => try out.writer.writeAll("'"), else => try out.writer.writeByte(c), }; return out.toOwnedSlice(); } fn displayHost(public_url: []const u8) []const u8 { var value = public_url; if (std.mem.startsWith(u8, value, "https://")) value = value["https://".len..]; if (std.mem.startsWith(u8, value, "http://")) value = value["http://".len..]; if (std.mem.indexOfScalar(u8, value, '/')) |idx| value = value[0..idx]; return value; }