From 1937f682ff20eab52159e585d863cff51edbfe9e Mon Sep 17 00:00:00 2001 From: celine Date: Wed, 28 Jan 2026 01:37:48 +0000 Subject: [PATCH] Merge remote-tracking branch 'origin/main' into feature/social-reader --- appview/index.ts | 24 ++++++++++++++++++++++++ lexicons/build.ts | 2 ++ supabase/database.types.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ lexicons/api/index.ts | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lexicons/api/lexicons.ts | 26 ++++++++++++++++++++++++++ lexicons/src/authFullPermissions.ts | 2 ++ supabase/migrations/20260127000000_add_recommends_table.sql | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lexicons/pub/leaflet/authFullPermissions.json | 3 ++- lexicons/src/interactions/index.ts | 21 +++++++++++++++++++++ lexicons/pub/leaflet/interactions/recommend.json | 28 ++++++++++++++++++++++++++++ lexicons/api/types/pub/leaflet/interactions/recommend.ts | 32 ++++++++++++++++++++++++++++++++ 11 file(s) changed, 344 insertion(s)(+), 1 deletion(s)(-) diff --git a/appview/index.ts b/appview/index.ts --- a/appview/index.ts +++ b/appview/index.ts @@ -11,6 +11,7 @@ PubLeafletComment, PubLeafletPollVote, PubLeafletPollDefinition, + PubLeafletInteractionsRecommend, SiteStandardDocument, SiteStandardPublication, SiteStandardGraphSubscription, @@ -48,6 +49,7 @@ ids.PubLeafletComment, ids.PubLeafletPollVote, ids.PubLeafletPollDefinition, + ids.PubLeafletInteractionsRecommend, // ids.AppBskyActorProfile, "app.bsky.feed.post", ids.SiteStandardDocument, @@ -206,6 +208,28 @@ if (evt.event === "delete") { await supabase .from("atp_poll_records") + .delete() + .eq("uri", evt.uri.toString()); + } + } + if (evt.collection === ids.PubLeafletInteractionsRecommend) { + if (evt.event === "create" || evt.event === "update") { + let record = PubLeafletInteractionsRecommend.validateRecord(evt.record); + if (!record.success) return; + await supabase + .from("identities") + .upsert({ atp_did: evt.did }, { onConflict: "atp_did" }); + let { error } = await supabase.from("recommends_on_documents").upsert({ + uri: evt.uri.toString(), + recommender_did: evt.did, + document: record.value.subject, + record: record.value as Json, + }); + if (error) console.log("Error upserting recommend:", error); + } + if (evt.event === "delete") { + await supabase + .from("recommends_on_documents") .delete() .eq("uri", evt.uri.toString()); } diff --git a/lexicons/build.ts b/lexicons/build.ts --- a/lexicons/build.ts +++ b/lexicons/build.ts @@ -3,6 +3,7 @@ import { PubLeafletDocument } from "./src/document"; import * as PublicationLexicons from "./src/publication"; import * as PollLexicons from "./src/polls"; +import * as InteractionsLexicons from "./src/interactions"; import { ThemeLexicons } from "./src/theme"; import * as fs from "fs"; @@ -31,6 +32,7 @@ ...BlockLexicons, ...Object.values(PublicationLexicons), ...Object.values(PollLexicons), + ...Object.values(InteractionsLexicons), ]; // Write each lexicon to a file diff --git a/supabase/database.types.ts b/supabase/database.types.ts --- a/supabase/database.types.ts +++ b/supabase/database.types.ts @@ -1075,6 +1075,45 @@ }, ] } + recommends_on_documents: { + Row: { + document: string + indexed_at: string + recommender_did: string + record: Json + uri: string + } + Insert: { + document: string + indexed_at?: string + recommender_did: string + record: Json + uri: string + } + Update: { + document?: string + indexed_at?: string + recommender_did?: string + record?: Json + uri?: string + } + Relationships: [ + { + foreignKeyName: "recommends_on_documents_document_fkey" + columns: ["document"] + isOneToOne: false + referencedRelation: "documents" + referencedColumns: ["uri"] + }, + { + foreignKeyName: "recommends_on_documents_recommender_did_fkey" + columns: ["recommender_did"] + isOneToOne: false + referencedRelation: "identities" + referencedColumns: ["atp_did"] + }, + ] + } replicache_clients: { Row: { client_group: string @@ -1303,6 +1342,12 @@ Returns: { like: unknown }[] + } + parse_iso_timestamp: { + Args: { + "": string + } + Returns: string } pull_data: { Args: { diff --git a/lexicons/api/index.ts b/lexicons/api/index.ts --- a/lexicons/api/index.ts +++ b/lexicons/api/index.ts @@ -41,6 +41,7 @@ import * as PubLeafletContent from './types/pub/leaflet/content' import * as PubLeafletDocument from './types/pub/leaflet/document' import * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' +import * as PubLeafletInteractionsRecommend from './types/pub/leaflet/interactions/recommend' import * as PubLeafletPagesCanvas from './types/pub/leaflet/pages/canvas' import * as PubLeafletPagesLinearDocument from './types/pub/leaflet/pages/linearDocument' import * as PubLeafletPollDefinition from './types/pub/leaflet/poll/definition' @@ -87,6 +88,7 @@ export * as PubLeafletContent from './types/pub/leaflet/content' export * as PubLeafletDocument from './types/pub/leaflet/document' export * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' +export * as PubLeafletInteractionsRecommend from './types/pub/leaflet/interactions/recommend' export * as PubLeafletPagesCanvas from './types/pub/leaflet/pages/canvas' export * as PubLeafletPagesLinearDocument from './types/pub/leaflet/pages/linearDocument' export * as PubLeafletPollDefinition from './types/pub/leaflet/poll/definition' @@ -408,6 +410,7 @@ publication: PubLeafletPublicationRecord blocks: PubLeafletBlocksNS graph: PubLeafletGraphNS + interactions: PubLeafletInteractionsNS pages: PubLeafletPagesNS poll: PubLeafletPollNS richtext: PubLeafletRichtextNS @@ -417,6 +420,7 @@ this._client = client this.blocks = new PubLeafletBlocksNS(client) this.graph = new PubLeafletGraphNS(client) + this.interactions = new PubLeafletInteractionsNS(client) this.pages = new PubLeafletPagesNS(client) this.poll = new PubLeafletPollNS(client) this.richtext = new PubLeafletRichtextNS(client) @@ -523,6 +527,99 @@ 'com.atproto.repo.deleteRecord', undefined, { collection: 'pub.leaflet.graph.subscription', ...params }, + { headers }, + ) + } +} + +export class PubLeafletInteractionsNS { + _client: XrpcClient + recommend: PubLeafletInteractionsRecommendRecord + + constructor(client: XrpcClient) { + this._client = client + this.recommend = new PubLeafletInteractionsRecommendRecord(client) + } +} + +export class PubLeafletInteractionsRecommendRecord { + _client: XrpcClient + + constructor(client: XrpcClient) { + this._client = client + } + + async list( + params: OmitKey, + ): Promise<{ + cursor?: string + records: { uri: string; value: PubLeafletInteractionsRecommend.Record }[] + }> { + const res = await this._client.call('com.atproto.repo.listRecords', { + collection: 'pub.leaflet.interactions.recommend', + ...params, + }) + return res.data + } + + async get( + params: OmitKey, + ): Promise<{ + uri: string + cid: string + value: PubLeafletInteractionsRecommend.Record + }> { + const res = await this._client.call('com.atproto.repo.getRecord', { + collection: 'pub.leaflet.interactions.recommend', + ...params, + }) + return res.data + } + + async create( + params: OmitKey< + ComAtprotoRepoCreateRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'pub.leaflet.interactions.recommend' + const res = await this._client.call( + 'com.atproto.repo.createRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async put( + params: OmitKey< + ComAtprotoRepoPutRecord.InputSchema, + 'collection' | 'record' + >, + record: Un$Typed, + headers?: Record, + ): Promise<{ uri: string; cid: string }> { + const collection = 'pub.leaflet.interactions.recommend' + const res = await this._client.call( + 'com.atproto.repo.putRecord', + undefined, + { collection, ...params, record: { ...record, $type: collection } }, + { encoding: 'application/json', headers }, + ) + return res.data + } + + async delete( + params: OmitKey, + headers?: Record, + ): Promise { + await this._client.call( + 'com.atproto.repo.deleteRecord', + undefined, + { collection: 'pub.leaflet.interactions.recommend', ...params }, { headers }, ) } diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts --- a/lexicons/api/lexicons.ts +++ b/lexicons/api/lexicons.ts @@ -1517,6 +1517,31 @@ }, }, }, + PubLeafletInteractionsRecommend: { + lexicon: 1, + id: 'pub.leaflet.interactions.recommend', + defs: { + main: { + type: 'record', + key: 'tid', + description: 'Record representing a recommend on a document', + record: { + type: 'object', + required: ['subject', 'createdAt'], + properties: { + subject: { + type: 'string', + format: 'at-uri', + }, + createdAt: { + type: 'string', + format: 'datetime', + }, + }, + }, + }, + }, + }, PubLeafletPagesCanvas: { lexicon: 1, id: 'pub.leaflet.pages.canvas', @@ -2418,6 +2443,7 @@ PubLeafletContent: 'pub.leaflet.content', PubLeafletDocument: 'pub.leaflet.document', PubLeafletGraphSubscription: 'pub.leaflet.graph.subscription', + PubLeafletInteractionsRecommend: 'pub.leaflet.interactions.recommend', PubLeafletPagesCanvas: 'pub.leaflet.pages.canvas', PubLeafletPagesLinearDocument: 'pub.leaflet.pages.linearDocument', PubLeafletPollDefinition: 'pub.leaflet.poll.definition', diff --git a/lexicons/src/authFullPermissions.ts b/lexicons/src/authFullPermissions.ts --- a/lexicons/src/authFullPermissions.ts +++ b/lexicons/src/authFullPermissions.ts @@ -6,6 +6,7 @@ } from "./publication"; import { PubLeafletComment } from "./comment"; import { PubLeafletPollDefinition, PubLeafletPollVote } from "./polls"; +import { PubLeafletInteractionsRecommend } from "./interactions"; export const PubLeafletAuthFullPermissions: LexiconDoc = { lexicon: 1, @@ -28,6 +29,7 @@ PubLeafletPollDefinition.id, PubLeafletPollVote.id, PubLeafletPublicationSubscription.id, + PubLeafletInteractionsRecommend.id, ], }, ], diff --git a/supabase/migrations/20260127000000_add_recommends_table.sql b/supabase/migrations/20260127000000_add_recommends_table.sql new file mode 100644 --- /dev/null +++ b/supabase/migrations/20260127000000_add_recommends_table.sql @@ -0,0 +1,65 @@ +create table "public"."recommends_on_documents" ( + "uri" text not null, + "record" jsonb not null, + "document" text not null, + "recommender_did" text not null, + "indexed_at" timestamp with time zone not null default now() +); + +alter table "public"."recommends_on_documents" enable row level security; + +CREATE UNIQUE INDEX recommends_on_documents_pkey ON public.recommends_on_documents USING btree (uri); + +alter table "public"."recommends_on_documents" add constraint "recommends_on_documents_pkey" PRIMARY KEY using index "recommends_on_documents_pkey"; + +CREATE INDEX recommends_on_documents_document_idx ON public.recommends_on_documents USING btree (document); + +CREATE INDEX recommends_on_documents_recommender_did_idx ON public.recommends_on_documents USING btree (recommender_did); + +CREATE UNIQUE INDEX recommends_on_documents_recommender_document_idx ON public.recommends_on_documents USING btree (recommender_did, document); + +alter table "public"."recommends_on_documents" add constraint "recommends_on_documents_document_fkey" FOREIGN KEY (document) REFERENCES documents(uri) ON UPDATE CASCADE ON DELETE CASCADE; + +alter table "public"."recommends_on_documents" add constraint "recommends_on_documents_recommender_did_fkey" FOREIGN KEY (recommender_did) REFERENCES identities(atp_did) ON UPDATE CASCADE ON DELETE CASCADE; + +grant delete on table "public"."recommends_on_documents" to "anon"; + +grant insert on table "public"."recommends_on_documents" to "anon"; + +grant references on table "public"."recommends_on_documents" to "anon"; + +grant select on table "public"."recommends_on_documents" to "anon"; + +grant trigger on table "public"."recommends_on_documents" to "anon"; + +grant truncate on table "public"."recommends_on_documents" to "anon"; + +grant update on table "public"."recommends_on_documents" to "anon"; + +grant delete on table "public"."recommends_on_documents" to "authenticated"; + +grant insert on table "public"."recommends_on_documents" to "authenticated"; + +grant references on table "public"."recommends_on_documents" to "authenticated"; + +grant select on table "public"."recommends_on_documents" to "authenticated"; + +grant trigger on table "public"."recommends_on_documents" to "authenticated"; + +grant truncate on table "public"."recommends_on_documents" to "authenticated"; + +grant update on table "public"."recommends_on_documents" to "authenticated"; + +grant delete on table "public"."recommends_on_documents" to "service_role"; + +grant insert on table "public"."recommends_on_documents" to "service_role"; + +grant references on table "public"."recommends_on_documents" to "service_role"; + +grant select on table "public"."recommends_on_documents" to "service_role"; + +grant trigger on table "public"."recommends_on_documents" to "service_role"; + +grant truncate on table "public"."recommends_on_documents" to "service_role"; + +grant update on table "public"."recommends_on_documents" to "service_role"; diff --git a/lexicons/pub/leaflet/authFullPermissions.json b/lexicons/pub/leaflet/authFullPermissions.json --- a/lexicons/pub/leaflet/authFullPermissions.json +++ b/lexicons/pub/leaflet/authFullPermissions.json @@ -21,7 +21,8 @@ "pub.leaflet.comment", "pub.leaflet.poll.definition", "pub.leaflet.poll.vote", - "pub.leaflet.graph.subscription" + "pub.leaflet.graph.subscription", + "pub.leaflet.interactions.recommend" ] } ] diff --git a/lexicons/src/interactions/index.ts b/lexicons/src/interactions/index.ts new file mode 100644 --- /dev/null +++ b/lexicons/src/interactions/index.ts @@ -0,0 +1,21 @@ +import { LexiconDoc } from "@atproto/lexicon"; + +export const PubLeafletInteractionsRecommend: LexiconDoc = { + lexicon: 1, + id: "pub.leaflet.interactions.recommend", + defs: { + main: { + type: "record", + key: "tid", + description: "Record representing a recommend on a document", + record: { + type: "object", + required: ["subject", "createdAt"], + properties: { + subject: { type: "string", format: "at-uri" }, + createdAt: { type: "string", format: "datetime" }, + }, + }, + }, + }, +}; diff --git a/lexicons/pub/leaflet/interactions/recommend.json b/lexicons/pub/leaflet/interactions/recommend.json new file mode 100644 --- /dev/null +++ b/lexicons/pub/leaflet/interactions/recommend.json @@ -0,0 +1,28 @@ +{ + "lexicon": 1, + "id": "pub.leaflet.interactions.recommend", + "defs": { + "main": { + "type": "record", + "key": "tid", + "description": "Record representing a recommend on a document", + "record": { + "type": "object", + "required": [ + "subject", + "createdAt" + ], + "properties": { + "subject": { + "type": "string", + "format": "at-uri" + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + } + } + } +} \ No newline at end of file diff --git a/lexicons/api/types/pub/leaflet/interactions/recommend.ts b/lexicons/api/types/pub/leaflet/interactions/recommend.ts new file mode 100644 --- /dev/null +++ b/lexicons/api/types/pub/leaflet/interactions/recommend.ts @@ -0,0 +1,32 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { type ValidationResult, BlobRef } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import { validate as _validate } from '../../../../lexicons' +import { + type $Typed, + is$typed as _is$typed, + type OmitKey, +} from '../../../../util' + +const is$typed = _is$typed, + validate = _validate +const id = 'pub.leaflet.interactions.recommend' + +export interface Record { + $type: 'pub.leaflet.interactions.recommend' + subject: string + createdAt: string + [k: string]: unknown +} + +const hashRecord = 'main' + +export function isRecord(v: V) { + return is$typed(v, id, hashRecord) +} + +export function validateRecord(v: V) { + return validate(v, id, hashRecord, true) +} -- tangled.sh