bsky feeds about music music-atmosphere-feed.plyr.fm/
bsky feed zig

restructure src/ for clarity

src/
├── feed/ ← customize these for your feed
│ ├── filter.zig
│ └── config.zig
├── server/ ← web layer
│ ├── http.zig
│ ├── dashboard.zig
│ └── stats.zig
└── stream/ ← ingestion
├── jetstream.zig
├── db.zig
└── atproto.zig

also: add pre-commit hook for zig fmt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+25 -42
+8
.githooks/pre-commit
··· 1 + #!/bin/sh 2 + # format zig files before commit 3 + 4 + zig fmt --check src/ build.zig 2>/dev/null 5 + if [ $? -ne 0 ]; then 6 + echo "zig fmt check failed. run 'zig fmt src/ build.zig' to fix." 7 + exit 1 8 + fi
src/atproto.zig src/stream/atproto.zig
src/config.zig src/feed/config.zig
+1 -1
src/dashboard.zig src/server/dashboard.zig
··· 1 1 const std = @import("std"); 2 2 const stats = @import("stats.zig"); 3 - const db = @import("db.zig"); 3 + const db = @import("../stream/db.zig"); 4 4 5 5 fn formatNumber(buf: []u8, n: u64) []const u8 { 6 6 var temp: [32]u8 = undefined;
+2 -3
src/db.zig src/stream/db.zig
··· 2 2 const posix = std.posix; 3 3 const zqlite = @import("zqlite"); 4 4 const zat = @import("zat"); 5 - const config = @import("config.zig"); 5 + const config = @import("../feed/config.zig"); 6 6 const atproto = @import("atproto.zig"); 7 7 8 8 var db: ?zqlite.Conn = null; ··· 10 10 11 11 // static buffer for db path (env vars return slices, zqlite needs sentinel) 12 12 var db_path_buf: [256]u8 = undefined; 13 - 14 13 15 14 pub fn init() !void { 16 15 const db_path_env = posix.getenv("DATABASE_PATH") orelse "/data/feed.db"; ··· 322 321 // lazy backfill 323 322 // ----------------------------------------------------------------------------- 324 323 325 - const filter = @import("filter.zig"); 324 + const filter = @import("../feed/filter.zig"); 326 325 327 326 /// check if an account has been backfilled 328 327 fn isBackfilled(did: []const u8) bool {
-22
src/feed.zig
··· 1 - const std = @import("std"); 2 - const Allocator = std.mem.Allocator; 3 - const db = @import("db.zig"); 4 - const config = @import("config.zig"); 5 - 6 - pub fn init() !void { 7 - try db.init(); 8 - } 9 - 10 - pub fn addPost(uri: []const u8, cid: []const u8) !void { 11 - try db.addPost(uri, cid); 12 - } 13 - 14 - pub fn getSkeleton( 15 - alloc: Allocator, 16 - feed_type: config.FeedType, 17 - requester_did: ?[]const u8, 18 - cursor: ?[]const u8, 19 - limit: usize, 20 - ) ![]const u8 { 21 - return db.getPosts(alloc, feed_type, requester_did, cursor, limit); 22 - }
+1 -1
src/filter.zig src/feed/filter.zig
··· 1 1 const std = @import("std"); 2 2 const mem = std.mem; 3 3 const json = std.json; 4 - const stats = @import("stats.zig"); 4 + const stats = @import("../server/stats.zig"); 5 5 6 6 const Record = json.ObjectMap; 7 7 const Filter = *const fn (Record) ?bool;
+4 -5
src/http.zig src/server/http.zig
··· 2 2 const net = std.net; 3 3 const http = std.http; 4 4 const mem = std.mem; 5 - const feed = @import("feed.zig"); 6 - const config = @import("config.zig"); 5 + const config = @import("../feed/config.zig"); 7 6 const dashboard = @import("dashboard.zig"); 8 - const atproto = @import("atproto.zig"); 7 + const atproto = @import("../stream/atproto.zig"); 9 8 const stats = @import("stats.zig"); 10 - const db = @import("db.zig"); 9 + const db = @import("../stream/db.zig"); 11 10 12 11 const HTTP_BUF_SIZE = 8192; 13 12 ··· 155 154 const limit_str = parseQueryParam(alloc, target, "limit") catch "20"; 156 155 const limit = std.fmt.parseInt(usize, limit_str, 10) catch 20; 157 156 158 - const result = feed.getSkeleton(alloc, feed_type, requester_did, cursor, limit) catch |err| { 157 + const result = db.getPosts(alloc, feed_type, requester_did, cursor, limit) catch |err| { 159 158 std.debug.print("feed error: {}\n", .{err}); 160 159 try sendJson(request, .internal_server_error, 161 160 \\{"error":"internal error"}
+3 -4
src/jetstream.zig src/stream/jetstream.zig
··· 4 4 const posix = std.posix; 5 5 const Allocator = mem.Allocator; 6 6 const websocket = @import("websocket"); 7 - const feed = @import("feed.zig"); 8 - const filter = @import("filter.zig"); 9 - const stats = @import("stats.zig"); 7 + const filter = @import("../feed/filter.zig"); 8 + const stats = @import("../server/stats.zig"); 10 9 const db = @import("db.zig"); 11 10 12 11 // turbostream: hydrated jetstream from graze.social ··· 159 158 if (at_uri != .string) return error.NotAPost; 160 159 161 160 // add to feed 162 - feed.addPost(at_uri.string, cid_val.string) catch |err| { 161 + db.addPost(at_uri.string, cid_val.string) catch |err| { 163 162 std.debug.print("failed to add post: {}\n", .{err}); 164 163 return; 165 164 };
+6 -6
src/main.zig
··· 2 2 const net = std.net; 3 3 const posix = std.posix; 4 4 const Thread = std.Thread; 5 - const http = @import("http.zig"); 6 - const feed = @import("feed.zig"); 7 - const jetstream = @import("jetstream.zig"); 8 - const stats = @import("stats.zig"); 5 + const http = @import("server/http.zig"); 6 + const db = @import("stream/db.zig"); 7 + const jetstream = @import("stream/jetstream.zig"); 8 + const stats = @import("server/stats.zig"); 9 9 10 10 const MAX_HTTP_WORKERS = 16; 11 11 const SOCKET_TIMEOUT_SECS = 30; ··· 18 18 // init stats 19 19 stats.init(); 20 20 21 - // init feed store (opens sqlite db) 22 - try feed.init(); 21 + // init database 22 + try db.init(); 23 23 24 24 // start jetstream consumer in background 25 25 const js_thread = try Thread.spawn(.{}, jetstream.consumer, .{allocator});
src/stats.zig src/server/stats.zig