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

feat: add notifications xrpc endpoint

Changed files
+190 -1
__generated__
types
social
grain
notification
lexicons
social
grain
notification
src
api
+22
__generated__/index.ts
··· 9 9 type StreamAuthVerifier, 10 10 } from "npm:@atproto/xrpc-server" 11 11 import { schemas } from './lexicons.ts' 12 + import * as SocialGrainNotificationGetNotifications from './types/social/grain/notification/getNotifications.ts' 12 13 import * as SocialGrainGalleryGetGalleryThread from './types/social/grain/gallery/getGalleryThread.ts' 13 14 import * as SocialGrainGalleryGetActorGalleries from './types/social/grain/gallery/getActorGalleries.ts' 14 15 import * as SocialGrainGalleryGetGallery from './types/social/grain/gallery/getGallery.ts' ··· 184 185 185 186 export class SocialGrainNS { 186 187 _server: Server 188 + notification: SocialGrainNotificationNS 187 189 gallery: SocialGrainGalleryNS 188 190 graph: SocialGrainGraphNS 189 191 labeler: SocialGrainLabelerNS ··· 193 195 194 196 constructor(server: Server) { 195 197 this._server = server 198 + this.notification = new SocialGrainNotificationNS(server) 196 199 this.gallery = new SocialGrainGalleryNS(server) 197 200 this.graph = new SocialGrainGraphNS(server) 198 201 this.labeler = new SocialGrainLabelerNS(server) 199 202 this.feed = new SocialGrainFeedNS(server) 200 203 this.actor = new SocialGrainActorNS(server) 201 204 this.photo = new SocialGrainPhotoNS(server) 205 + } 206 + } 207 + 208 + export class SocialGrainNotificationNS { 209 + _server: Server 210 + 211 + constructor(server: Server) { 212 + this._server = server 213 + } 214 + 215 + getNotifications<AV extends AuthVerifier>( 216 + cfg: ConfigOf< 217 + AV, 218 + SocialGrainNotificationGetNotifications.Handler<ExtractAuth<AV>>, 219 + SocialGrainNotificationGetNotifications.HandlerReqCtx<ExtractAuth<AV>> 220 + >, 221 + ) { 222 + const nsid = 'social.grain.notification.getNotifications' // @ts-ignore 223 + return this._server.xrpc.method(nsid, cfg) 202 224 } 203 225 } 204 226
+50
__generated__/lexicons.ts
··· 2473 2473 }, 2474 2474 }, 2475 2475 }, 2476 + SocialGrainNotificationGetNotifications: { 2477 + lexicon: 1, 2478 + id: 'social.grain.notification.getNotifications', 2479 + defs: { 2480 + main: { 2481 + type: 'query', 2482 + description: 2483 + 'Enumerate notifications for the requesting account. Requires auth.', 2484 + parameters: { 2485 + type: 'params', 2486 + properties: { 2487 + limit: { 2488 + type: 'integer', 2489 + minimum: 1, 2490 + maximum: 100, 2491 + default: 50, 2492 + }, 2493 + cursor: { 2494 + type: 'string', 2495 + }, 2496 + }, 2497 + }, 2498 + output: { 2499 + encoding: 'application/json', 2500 + schema: { 2501 + type: 'object', 2502 + required: ['notifications'], 2503 + properties: { 2504 + cursor: { 2505 + type: 'string', 2506 + }, 2507 + notifications: { 2508 + type: 'array', 2509 + items: { 2510 + type: 'ref', 2511 + ref: 'lex:social.grain.notification.defs#notificationView', 2512 + }, 2513 + }, 2514 + seenAt: { 2515 + type: 'string', 2516 + format: 'datetime', 2517 + }, 2518 + }, 2519 + }, 2520 + }, 2521 + }, 2522 + }, 2523 + }, 2476 2524 SocialGrainCommentDefs: { 2477 2525 lexicon: 1, 2478 2526 id: 'social.grain.comment.defs', ··· 3820 3868 ShTangledActorProfile: 'sh.tangled.actor.profile', 3821 3869 SocialGrainDefs: 'social.grain.defs', 3822 3870 SocialGrainNotificationDefs: 'social.grain.notification.defs', 3871 + SocialGrainNotificationGetNotifications: 3872 + 'social.grain.notification.getNotifications', 3823 3873 SocialGrainCommentDefs: 'social.grain.comment.defs', 3824 3874 SocialGrainComment: 'social.grain.comment', 3825 3875 SocialGrainGalleryItem: 'social.grain.gallery.item',
+51
__generated__/types/social/grain/notification/getNotifications.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { HandlerAuth, HandlerPipeThrough } 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 + import type * as SocialGrainNotificationDefs from "./defs.ts"; 9 + 10 + const is$typed = _is$typed, 11 + validate = _validate; 12 + const id = "social.grain.notification.getNotifications"; 13 + 14 + export interface QueryParams { 15 + limit: number; 16 + cursor?: string; 17 + } 18 + 19 + export type InputSchema = undefined; 20 + 21 + export interface OutputSchema { 22 + cursor?: string; 23 + notifications: SocialGrainNotificationDefs.NotificationView[]; 24 + seenAt?: string; 25 + } 26 + 27 + export type HandlerInput = undefined; 28 + 29 + export interface HandlerSuccess { 30 + encoding: "application/json"; 31 + body: OutputSchema; 32 + headers?: { [key: string]: string }; 33 + } 34 + 35 + export interface HandlerError { 36 + status: number; 37 + message?: string; 38 + } 39 + 40 + export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough; 41 + export type HandlerReqCtx<HA extends HandlerAuth = never> = { 42 + auth: HA; 43 + params: QueryParams; 44 + input: HandlerInput; 45 + req: express.Request; 46 + res: express.Response; 47 + resetRouteRateLimits: () => Promise<void>; 48 + }; 49 + export type Handler<HA extends HandlerAuth = never> = ( 50 + ctx: HandlerReqCtx<HA>, 51 + ) => Promise<HandlerOutput> | HandlerOutput;
+1 -1
deno.json
··· 29 29 "dev:tailwind": "deno run -A --node-modules-dir npm:@tailwindcss/cli -i ./src/input.css -o ./build/styles.css --watch", 30 30 "dev:fonts": "rm -rf ./build/fonts && cp -r ./static/fonts/. ./build/fonts", 31 31 "sync": "deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.40 sync --collections=social.grain.gallery,social.grain.actor.profile,social.grain.photo,social.grain.favorite,social.grain.gallery.item,social.grain.graph.follow,social.grain.photo.exif,social.grain.comment --external-collections=app.bsky.actor.profile,app.bsky.graph.follow,sh.tangled.graph.follow,sh.tangled.actor.profile --collection-key-map=\"{\\\"social.grain.favorite\\\":[\\\"subject\\\"],\\\"social.grain.graph.follow\\\":[\\\"subject\\\"],\\\"social.grain.gallery.item\\\":[\\\"gallery\\\",\\\"item\\\"],\\\"social.grain.photo.exif\\\":[\\\"photo\\\"],\\\"social.grain.comment\\\":[\\\"subject\\\"]}\"", 32 - "codegen": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.40 lexgen" 32 + "codegen": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.42 lexgen" 33 33 }, 34 34 "compilerOptions": { 35 35 "jsx": "precompile",
+40
lexicons/social/grain/notification/getNotifications.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.notification.getNotifications", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Enumerate notifications for the requesting account. Requires auth.", 8 + "parameters": { 9 + "type": "params", 10 + "properties": { 11 + "limit": { 12 + "type": "integer", 13 + "minimum": 1, 14 + "maximum": 100, 15 + "default": 50 16 + }, 17 + "cursor": { "type": "string" } 18 + } 19 + }, 20 + "output": { 21 + "encoding": "application/json", 22 + "schema": { 23 + "type": "object", 24 + "required": ["notifications"], 25 + "properties": { 26 + "cursor": { "type": "string" }, 27 + "notifications": { 28 + "type": "array", 29 + "items": { 30 + "type": "ref", 31 + "ref": "social.grain.notification.defs#notificationView" 32 + } 33 + }, 34 + "seenAt": { "type": "string", "format": "datetime" } 35 + } 36 + } 37 + } 38 + } 39 + } 40 + }
+26
src/api/mod.ts
··· 17 17 OutputSchema as GetGalleryThreadOutputSchema, 18 18 QueryParams as GetGalleryThreadQueryParams, 19 19 } from "$lexicon/types/social/grain/gallery/getGalleryThread.ts"; 20 + import { 21 + OutputSchema as GetNotificationsOutputSchema, 22 + } from "$lexicon/types/social/grain/notification/getNotifications.ts"; 20 23 import { AtUri } from "@atproto/syntax"; 21 24 import { BffMiddleware, OAUTH_ROUTES, route } from "@bigmoves/bff"; 22 25 import { getActorGalleries, getActorProfileDetailed } from "../lib/actor.ts"; 23 26 import { BadRequestError } from "../lib/errors.ts"; 24 27 import { getGallery } from "../lib/gallery.ts"; 28 + import { getNotifications } from "../lib/notifications.ts"; 25 29 import { getTimeline } from "../lib/timeline.ts"; 26 30 import { getGalleryComments } from "../modules/comments.tsx"; 27 31 ··· 100 104 { feed: items.map((i) => i.gallery) } as GetTimelineOutputSchema, 101 105 ); 102 106 }), 107 + route( 108 + "/xrpc/social.grain.notification.getNotifications", 109 + (_req, _params, ctx) => { 110 + // @TODO: this redirects, we should have a json response 111 + ctx.requireAuth(); 112 + const notifications = getNotifications( 113 + ctx, 114 + ); 115 + return ctx.json( 116 + { notifications } as GetNotificationsOutputSchema, 117 + ); 118 + }, 119 + ), 103 120 ]; 104 121 105 122 function getProfileQueryParams(url: URL): GetProfileQueryParams { ··· 130 147 if (!uri) throw new BadRequestError("Missing uri parameter"); 131 148 return { uri }; 132 149 } 150 + 151 + // function getNotificationsQueryParams(url: URL): GetNotificationsQueryParams { 152 + // const limit = parseInt(url.searchParams.get("limit") ?? "50", 10); 153 + // if (isNaN(limit) || limit <= 0) { 154 + // throw new BadRequestError("Invalid limit parameter"); 155 + // } 156 + // const cursor = url.searchParams.get("cursor") ?? undefined; 157 + // return { limit, cursor }; 158 + // } 133 159 134 160 // function getTimelineQueryParams(url: URL): GetTimelineQueryParams { 135 161 // const algorithm = url.searchParams.get("algorithm") ?? undefined;