+2
-71
src/db.zig
+2
-71
src/db.zig
···
10
// static buffer for db path (env vars return slices, zqlite needs sentinel)
11
var db_path_buf: [256]u8 = undefined;
12
13
-
// follows cache TTL: 5 minutes
14
-
const FOLLOWS_CACHE_TTL_MS: i64 = 5 * 60 * 1000;
15
16
pub fn init() !void {
17
const db_path_env = posix.getenv("DATABASE_PATH") orelse "/data/feed.db";
···
249
return rest[0..end];
250
}
251
252
-
/// get follows set for a DID, using cache if fresh
253
fn getFollowsSet(alloc: std.mem.Allocator, requester_did: []const u8) !std.StringHashMap(void) {
254
var set = std.StringHashMap(void).init(alloc);
255
errdefer set.deinit();
256
257
-
const now = std.time.milliTimestamp();
258
-
259
-
// check cache freshness
260
-
mutex.lock();
261
-
const conn = db orelse {
262
-
mutex.unlock();
263
-
return error.NotInitialized;
264
-
};
265
-
266
-
var cache_fresh = false;
267
-
var cache_rows = conn.rows(
268
-
"SELECT cached_at FROM follows_cache WHERE requester_did = ? LIMIT 1",
269
-
.{requester_did},
270
-
) catch {
271
-
mutex.unlock();
272
-
return set;
273
-
};
274
-
275
-
if (cache_rows.next()) |row| {
276
-
const cached_at = row.int(0);
277
-
if (now - cached_at < FOLLOWS_CACHE_TTL_MS) {
278
-
cache_fresh = true;
279
-
}
280
-
}
281
-
cache_rows.deinit();
282
-
283
-
if (cache_fresh) {
284
-
// load from cache
285
-
var follows_rows = conn.rows(
286
-
"SELECT follows_did FROM follows_cache WHERE requester_did = ?",
287
-
.{requester_did},
288
-
) catch {
289
-
mutex.unlock();
290
-
return set;
291
-
};
292
-
293
-
while (follows_rows.next()) |row| {
294
-
const did = row.text(0);
295
-
const owned = try alloc.dupe(u8, did);
296
-
try set.put(owned, {});
297
-
}
298
-
follows_rows.deinit();
299
-
mutex.unlock();
300
-
301
-
std.debug.print("loaded {d} follows from cache for {s}\n", .{ set.count(), requester_did });
302
-
return set;
303
-
}
304
-
mutex.unlock();
305
-
306
-
// fetch fresh follows from API
307
std.debug.print("fetching follows for {s}...\n", .{requester_did});
308
const follows = atproto.getFollows(alloc, requester_did) catch |err| {
309
std.debug.print("failed to fetch follows: {}\n", .{err});
···
311
};
312
defer alloc.free(follows);
313
314
-
// parse follows and populate set
315
var it = std.mem.splitScalar(u8, follows, '\n');
316
while (it.next()) |did| {
317
if (did.len == 0) continue;
···
321
322
std.debug.print("fetched {d} follows for {s}\n", .{ set.count(), requester_did });
323
324
-
// trigger lazy backfill for follows we haven't seen before
325
var dids_to_backfill: std.ArrayList([]const u8) = .empty;
326
defer dids_to_backfill.deinit(alloc);
327
var key_it = set.keyIterator();
···
330
}
331
if (dids_to_backfill.items.len > 0) {
332
triggerBackfill(alloc, dids_to_backfill.items);
333
-
}
334
-
335
-
// update cache
336
-
mutex.lock();
337
-
defer mutex.unlock();
338
-
339
-
// clear old cache for this user
340
-
conn.exec("DELETE FROM follows_cache WHERE requester_did = ?", .{requester_did}) catch {};
341
-
342
-
// insert new follows
343
-
var set_it = set.keyIterator();
344
-
while (set_it.next()) |key| {
345
-
conn.exec(
346
-
"INSERT INTO follows_cache (requester_did, follows_did, cached_at) VALUES (?, ?, ?)",
347
-
.{ requester_did, key.*, now },
348
-
) catch {};
349
}
350
351
return set;
···
10
// static buffer for db path (env vars return slices, zqlite needs sentinel)
11
var db_path_buf: [256]u8 = undefined;
12
13
14
pub fn init() !void {
15
const db_path_env = posix.getenv("DATABASE_PATH") orelse "/data/feed.db";
···
247
return rest[0..end];
248
}
249
250
+
/// get follows set for a DID
251
fn getFollowsSet(alloc: std.mem.Allocator, requester_did: []const u8) !std.StringHashMap(void) {
252
var set = std.StringHashMap(void).init(alloc);
253
errdefer set.deinit();
254
255
std.debug.print("fetching follows for {s}...\n", .{requester_did});
256
const follows = atproto.getFollows(alloc, requester_did) catch |err| {
257
std.debug.print("failed to fetch follows: {}\n", .{err});
···
259
};
260
defer alloc.free(follows);
261
262
var it = std.mem.splitScalar(u8, follows, '\n');
263
while (it.next()) |did| {
264
if (did.len == 0) continue;
···
268
269
std.debug.print("fetched {d} follows for {s}\n", .{ set.count(), requester_did });
270
271
+
// trigger backfill for follows we haven't seen before
272
var dids_to_backfill: std.ArrayList([]const u8) = .empty;
273
defer dids_to_backfill.deinit(alloc);
274
var key_it = set.keyIterator();
···
277
}
278
if (dids_to_backfill.items.len > 0) {
279
triggerBackfill(alloc, dids_to_backfill.items);
280
}
281
282
return set;