fuzzy find my records ken.waow.tech
embeddings pds search
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

add profile record: lexicon, putRecord/getRecord, auto-write on sign-in

- tech.waow.ken.profile lexicon (rkey self, createdAt) — presence
signal so indexers can discover ken users
- oauth.putRecord: idempotent create-or-update for fixed-rkey records
- pds.getRecord: unauthenticated single-record lookup
- server.ensureProfile: best-effort write on first /api/me per session,
skips if record already exists on the user's PDS
- scope updated to include repo:tech.waow.ken.profile

no auth or frontend changes — existing single-cookie flow untouched.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+128 -2
+28 -1
backend/src/oauth.zig
··· 22 22 // parameter in handleLogin/handleCallback 23 23 const store = @import("state.zig"); 24 24 25 - pub const SCOPE = "atproto repo:tech.waow.ken.pack blob:*/*"; 25 + pub const SCOPE = "atproto repo:tech.waow.ken.pack repo:tech.waow.ken.profile blob:*/*"; 26 26 27 27 // runtime config — set by server.zig on init via envConfig() 28 28 pub const Config = struct { ··· 1120 1120 defer alloc.free(resp); 1121 1121 // PDS returns a small JSON envelope on success; we don't need its fields. 1122 1122 } 1123 + 1124 + /// create or update a record at `collection/<rkey>`. used for records with a 1125 + /// well-known rkey (e.g. "self") where we want idempotent writes. 1126 + pub fn putRecord( 1127 + alloc: Allocator, 1128 + session: store.Session, 1129 + collection: []const u8, 1130 + rkey: []const u8, 1131 + record_json: []const u8, 1132 + ) !void { 1133 + const body = try std.fmt.allocPrint( 1134 + alloc, 1135 + "{{\"repo\":\"{s}\",\"collection\":\"{s}\",\"rkey\":\"{s}\",\"record\":{s}}}", 1136 + .{ session.did, collection, rkey, record_json }, 1137 + ); 1138 + defer alloc.free(body); 1139 + 1140 + const resp = try pdsAuthedRequest( 1141 + alloc, 1142 + session, 1143 + "POST", 1144 + "/xrpc/com.atproto.repo.putRecord", 1145 + body, 1146 + "application/json", 1147 + ); 1148 + defer alloc.free(resp); 1149 + }
+25
backend/src/pds.zig
··· 198 198 return .{ .records = out_records.items, .cursor = cursor_out }; 199 199 } 200 200 201 + /// fetch a single record by collection + rkey. unauthenticated. returns the 202 + /// record value (json.Value) or null if the record doesn't exist (404/400). 203 + pub fn getRecord( 204 + arena: Allocator, 205 + transport: *zat.HttpTransport, 206 + pds: []const u8, 207 + did: []const u8, 208 + collection: []const u8, 209 + rkey: []const u8, 210 + ) ?json.Value { 211 + const url = std.fmt.allocPrint( 212 + arena, 213 + "{s}/xrpc/com.atproto.repo.getRecord?repo={s}&collection={s}&rkey={s}", 214 + .{ pds, did, collection, rkey }, 215 + ) catch return null; 216 + 217 + const result = transport.fetch(.{ .url = url }) catch return null; 218 + if (result.status != .ok) return null; 219 + 220 + const parsed = json.parseFromSliceLeaky(json.Value, arena, result.body, .{}) catch 221 + return null; 222 + 223 + return parsed.object.get("value"); 224 + } 225 + 201 226 /// fetch a blob by cid via com.atproto.sync.getBlob. unauthenticated — blobs 202 227 /// in a public PDS are readable by anyone. returns the raw bytes owned by 203 228 /// `arena`.
+54 -1
backend/src/server.zig
··· 38 38 // see the header comment in the file itself. 39 39 const NSID_LOGO_JS = @embedFile("assets/nsid-logo.js"); 40 40 41 + const PROFILE_COLLECTION = "tech.waow.ken.profile"; 42 + 43 + /// DIDs for which we've already attempted a profile write this process lifetime. 44 + var profile_attempted: std.StringHashMapUnmanaged(void) = .empty; 45 + var profile_mutex: Io.Mutex = .init; 46 + 41 47 // ---------- app state ---------- 42 48 43 49 pub const App = struct { ··· 279 285 // ---------- route: /api/me ---------- 280 286 281 287 /// returns {handle, did} for the authenticated session, or 401 otherwise. 282 - /// the frontend uses this to decide whether to show the signed-in UI. 288 + /// on first call per session, best-effort writes a tech.waow.ken.profile/self 289 + /// record to the user's PDS (presence signal for indexers). 283 290 fn handleMe(request: *http.Server.Request, app: *App) !void { 284 291 var arena = std.heap.ArenaAllocator.init(app.allocator); 285 292 defer arena.deinit(); ··· 294 301 return; 295 302 }; 296 303 304 + ensureProfile(alloc, app, session); 305 + 297 306 var buf: std.ArrayList(u8) = .empty; 298 307 try buf.appendSlice(alloc, "{\"handle\":"); 299 308 try writeJsonString(&buf, alloc, session.handle); ··· 301 310 try writeJsonString(&buf, alloc, session.did); 302 311 try buf.appendSlice(alloc, "}"); 303 312 try sendJson(request, buf.items); 313 + } 314 + 315 + /// best-effort: if we haven't tried yet for this DID, check whether 316 + /// tech.waow.ken.profile/self exists and write it if absent. failures 317 + /// are logged and swallowed — the profile is a presence signal, not a gate. 318 + fn ensureProfile(alloc: Allocator, app: *App, session: store.Session) void { 319 + { 320 + profile_mutex.lockUncancelable(app.io); 321 + defer profile_mutex.unlock(app.io); 322 + if (profile_attempted.contains(session.did)) return; 323 + profile_attempted.put(app.allocator, session.did, {}) catch return; 324 + } 325 + 326 + var transport = zat.HttpTransport.init(app.io, alloc); 327 + defer transport.deinit(); 328 + if (pds.getRecord(alloc, &transport, session.pds_url, session.did, PROFILE_COLLECTION, "self")) |_| { 329 + return; // already exists 330 + } 331 + 332 + const now_secs: i64 = @intCast(@divTrunc( 333 + Io.Timestamp.now(app.io, .real).nanoseconds, 334 + std.time.ns_per_s, 335 + )); 336 + const epoch_secs: std.time.epoch.EpochSeconds = .{ .secs = @intCast(now_secs) }; 337 + const day = epoch_secs.getDaySeconds(); 338 + const year_day = epoch_secs.getEpochDay().calculateYearDay(); 339 + const md = year_day.calculateMonthDay(); 340 + 341 + var record: std.ArrayList(u8) = .empty; 342 + record.print(alloc, 343 + \\{{"$type":"{s}","createdAt":"{d:0>4}-{d:0>2}-{d:0>2}T{d:0>2}:{d:0>2}:{d:0>2}.000Z"}} 344 + , .{ 345 + PROFILE_COLLECTION, 346 + year_day.year, 347 + @intFromEnum(md.month), 348 + md.day_index + 1, 349 + day.getHoursIntoDay(), 350 + day.getMinutesIntoHour(), 351 + day.getSecondsIntoMinute(), 352 + }) catch return; 353 + 354 + oauth.putRecord(alloc, session, PROFILE_COLLECTION, "self", record.items) catch |err| { 355 + std.log.warn("profile auto-write failed for {s}: {}", .{ session.did, err }); 356 + }; 304 357 } 305 358 306 359 // ---------- routes: /api/pack/save + /api/pack/delete ----------
+21
lexicons/tech/waow/ken/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "tech.waow.ken.profile", 4 + "description": "Presence record indicating this DID uses ken. Written with rkey 'self'. Existence means opted in; deletion means opted out.", 5 + "defs": { 6 + "main": { 7 + "type": "record", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "required": ["createdAt"], 12 + "properties": { 13 + "createdAt": { 14 + "type": "string", 15 + "format": "datetime" 16 + } 17 + } 18 + } 19 + } 20 + } 21 + }