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 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, \\ \\ \\ \\ \\ \\ \\ zds \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\
    \\
    \\ zds \\ \\
    \\
    \\

    {s}

    \\

    an atproto personal data server.

    \\ {s} \\
    \\ {s} \\
    \\
    \\

    residents

    \\
      {s}
    \\
    \\
    \\

    collections

    \\
      {s}
    \\
    \\
    \\
    \\ \\ \\ \\ \\ , .{ 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; }