grain.social is a photo sharing platform built on atproto.

feat: add updateSeen notification procedure and related types; implement route and handler for updating seen notifications

+118 -7
+12
__generated__/index.ts
··· 9 9 type StreamAuthVerifier, 10 10 } from "npm:@atproto/xrpc-server" 11 11 import { schemas } from './lexicons.ts' 12 + import * as SocialGrainNotificationUpdateSeen from './types/social/grain/notification/updateSeen.ts' 12 13 import * as SocialGrainNotificationGetNotifications from './types/social/grain/notification/getNotifications.ts' 13 14 import * as SocialGrainGalleryGetGalleryThread from './types/social/grain/gallery/getGalleryThread.ts' 14 15 import * as SocialGrainGalleryGetActorGalleries from './types/social/grain/gallery/getActorGalleries.ts' ··· 215 216 216 217 constructor(server: Server) { 217 218 this._server = server 219 + } 220 + 221 + updateSeen<AV extends AuthVerifier>( 222 + cfg: ConfigOf< 223 + AV, 224 + SocialGrainNotificationUpdateSeen.Handler<ExtractAuth<AV>>, 225 + SocialGrainNotificationUpdateSeen.HandlerReqCtx<ExtractAuth<AV>> 226 + >, 227 + ) { 228 + const nsid = 'social.grain.notification.updateSeen' // @ts-ignore 229 + return this._server.xrpc.method(nsid, cfg) 218 230 } 219 231 220 232 getNotifications<AV extends AuthVerifier>(
+25
__generated__/lexicons.ts
··· 2531 2531 }, 2532 2532 }, 2533 2533 }, 2534 + SocialGrainNotificationUpdateSeen: { 2535 + lexicon: 1, 2536 + id: 'social.grain.notification.updateSeen', 2537 + defs: { 2538 + main: { 2539 + type: 'procedure', 2540 + description: 2541 + 'Notify server that the requesting account has seen notifications. Requires auth.', 2542 + input: { 2543 + encoding: 'application/json', 2544 + schema: { 2545 + type: 'object', 2546 + required: ['seenAt'], 2547 + properties: { 2548 + seenAt: { 2549 + type: 'string', 2550 + format: 'datetime', 2551 + }, 2552 + }, 2553 + }, 2554 + }, 2555 + }, 2556 + }, 2557 + }, 2534 2558 SocialGrainNotificationGetNotifications: { 2535 2559 lexicon: 1, 2536 2560 id: 'social.grain.notification.getNotifications', ··· 4245 4269 ShTangledActorProfile: 'sh.tangled.actor.profile', 4246 4270 SocialGrainDefs: 'social.grain.defs', 4247 4271 SocialGrainNotificationDefs: 'social.grain.notification.defs', 4272 + SocialGrainNotificationUpdateSeen: 'social.grain.notification.updateSeen', 4248 4273 SocialGrainNotificationGetNotifications: 4249 4274 'social.grain.notification.getNotifications', 4250 4275 SocialGrainCommentDefs: 'social.grain.comment.defs',
+40
__generated__/types/social/grain/notification/updateSeen.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { HandlerAuth } from "npm:@atproto/xrpc-server"; 5 + import express from "npm:express"; 6 + import { validate as _validate } from "../../../../lexicons.ts"; 7 + import { is$typed as _is$typed } from "../../../../util.ts"; 8 + 9 + const is$typed = _is$typed, 10 + validate = _validate; 11 + const id = "social.grain.notification.updateSeen"; 12 + 13 + export interface QueryParams {} 14 + 15 + export interface InputSchema { 16 + seenAt: string; 17 + } 18 + 19 + export interface HandlerInput { 20 + encoding: "application/json"; 21 + body: InputSchema; 22 + } 23 + 24 + export interface HandlerError { 25 + status: number; 26 + message?: string; 27 + } 28 + 29 + export type HandlerOutput = HandlerError | void; 30 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 31 + auth: HA; 32 + params: QueryParams; 33 + input: HandlerInput; 34 + req: express.Request; 35 + res: express.Response; 36 + resetRouteRateLimits: () => Promise<void>; 37 + }; 38 + export type Handler<HA extends HandlerAuth = never> = ( 39 + ctx: HandlerReqCtx<HA>, 40 + ) => Promise<HandlerOutput> | HandlerOutput;
+1 -1
deno.json
··· 3 3 "$lexicon/": "./__generated__/", 4 4 "@atproto/api": "npm:@atproto/api@^0.15.16", 5 5 "@atproto/syntax": "npm:@atproto/syntax@^0.4.0", 6 - "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.44", 6 + "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.45", 7 7 "@std/http": "jsr:@std/http@^1.0.17", 8 8 "@std/path": "jsr:@std/path@^1.0.9", 9 9 "@tailwindcss/cli": "npm:@tailwindcss/cli@^4.1.4",
+5 -5
deno.lock
··· 2 2 "version": "5", 3 3 "specifiers": { 4 4 "jsr:@bigmoves/atproto-oauth-client@0.2": "0.2.0", 5 - "jsr:@bigmoves/bff@0.3.0-beta.44": "0.3.0-beta.44", 5 + "jsr:@bigmoves/bff@0.3.0-beta.45": "0.3.0-beta.45", 6 6 "jsr:@deno/gfm@0.10": "0.10.0", 7 7 "jsr:@denosaurs/emoji@0.3": "0.3.1", 8 8 "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", ··· 23 23 "jsr:@std/fs@^1.0.19": "1.0.19", 24 24 "jsr:@std/html@^1.0.4": "1.0.4", 25 25 "jsr:@std/http@^1.0.13": "1.0.19", 26 - "jsr:@std/http@^1.0.17": "1.0.18", 26 + "jsr:@std/http@^1.0.17": "1.0.19", 27 27 "jsr:@std/internal@^1.0.6": "1.0.9", 28 28 "jsr:@std/internal@^1.0.9": "1.0.9", 29 29 "jsr:@std/media-types@^1.1.0": "1.1.0", ··· 100 100 "npm:jose" 101 101 ] 102 102 }, 103 - "@bigmoves/bff@0.3.0-beta.44": { 104 - "integrity": "178baae55f6466f3724cffdf3b267c76745c2434d95b5d76fbef1ba53e5428b7", 103 + "@bigmoves/bff@0.3.0-beta.45": { 104 + "integrity": "c45b461578e6a3e6f37fb93a3f62a11aa67462384d6fd1f65e442364082eaf14", 105 105 "dependencies": [ 106 106 "jsr:@bigmoves/atproto-oauth-client", 107 107 "jsr:@std/assert@^1.0.13", ··· 2129 2129 }, 2130 2130 "workspace": { 2131 2131 "dependencies": [ 2132 - "jsr:@bigmoves/bff@0.3.0-beta.44", 2132 + "jsr:@bigmoves/bff@0.3.0-beta.45", 2133 2133 "jsr:@std/http@^1.0.17", 2134 2134 "jsr:@std/path@^1.0.9", 2135 2135 "npm:@atproto/api@~0.15.16",
+20
lexicons/social/grain/notification/updateSeen.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.notification.updateSeen", 4 + "defs": { 5 + "main": { 6 + "type": "procedure", 7 + "description": "Notify server that the requesting account has seen notifications. Requires auth.", 8 + "input": { 9 + "encoding": "application/json", 10 + "schema": { 11 + "type": "object", 12 + "required": ["seenAt"], 13 + "properties": { 14 + "seenAt": { "type": "string", "format": "datetime" } 15 + } 16 + } 17 + } 18 + } 19 + } 20 + }
+14
src/api/mod.ts
··· 199 199 followers, 200 200 } as GetFollowersOutputSchema); 201 201 }), 202 + route( 203 + "/xrpc/social.grain.notification.updateSeen", 204 + ["POST"], 205 + async (req, _params, ctx) => { 206 + ctx.requireAuth(); 207 + const json = await req.json(); 208 + const seenAt = json.seenAt as string ?? undefined; 209 + if (!seenAt) { 210 + throw new BadRequestError("Missing seenAt input"); 211 + } 212 + ctx.updateSeen(seenAt); 213 + return ctx.json(null); 214 + }, 215 + ), 202 216 ]; 203 217 204 218 function getProfileQueryParams(url: URL): GetProfileQueryParams {
+1 -1
src/routes/actions.tsx
··· 34 34 ctx: BffContext<State>, 35 35 ) => { 36 36 ctx.requireAuth(); 37 - ctx.updateSeen(); 37 + ctx.updateSeen(new Date().toISOString()); 38 38 return new Response(null, { status: 200 }); 39 39 }; 40 40