a tool for shared writing and social publishing

Merge remote-tracking branch 'origin/main' into feature/social-reader

+344 -1
+24
appview/index.ts
··· 11 11 PubLeafletComment, 12 12 PubLeafletPollVote, 13 13 PubLeafletPollDefinition, 14 + PubLeafletInteractionsRecommend, 14 15 SiteStandardDocument, 15 16 SiteStandardPublication, 16 17 SiteStandardGraphSubscription, ··· 48 49 ids.PubLeafletComment, 49 50 ids.PubLeafletPollVote, 50 51 ids.PubLeafletPollDefinition, 52 + ids.PubLeafletInteractionsRecommend, 51 53 // ids.AppBskyActorProfile, 52 54 "app.bsky.feed.post", 53 55 ids.SiteStandardDocument, ··· 206 208 if (evt.event === "delete") { 207 209 await supabase 208 210 .from("atp_poll_records") 211 + .delete() 212 + .eq("uri", evt.uri.toString()); 213 + } 214 + } 215 + if (evt.collection === ids.PubLeafletInteractionsRecommend) { 216 + if (evt.event === "create" || evt.event === "update") { 217 + let record = PubLeafletInteractionsRecommend.validateRecord(evt.record); 218 + if (!record.success) return; 219 + await supabase 220 + .from("identities") 221 + .upsert({ atp_did: evt.did }, { onConflict: "atp_did" }); 222 + let { error } = await supabase.from("recommends_on_documents").upsert({ 223 + uri: evt.uri.toString(), 224 + recommender_did: evt.did, 225 + document: record.value.subject, 226 + record: record.value as Json, 227 + }); 228 + if (error) console.log("Error upserting recommend:", error); 229 + } 230 + if (evt.event === "delete") { 231 + await supabase 232 + .from("recommends_on_documents") 209 233 .delete() 210 234 .eq("uri", evt.uri.toString()); 211 235 }
+97
lexicons/api/index.ts
··· 41 41 import * as PubLeafletContent from './types/pub/leaflet/content' 42 42 import * as PubLeafletDocument from './types/pub/leaflet/document' 43 43 import * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' 44 + import * as PubLeafletInteractionsRecommend from './types/pub/leaflet/interactions/recommend' 44 45 import * as PubLeafletPagesCanvas from './types/pub/leaflet/pages/canvas' 45 46 import * as PubLeafletPagesLinearDocument from './types/pub/leaflet/pages/linearDocument' 46 47 import * as PubLeafletPollDefinition from './types/pub/leaflet/poll/definition' ··· 87 88 export * as PubLeafletContent from './types/pub/leaflet/content' 88 89 export * as PubLeafletDocument from './types/pub/leaflet/document' 89 90 export * as PubLeafletGraphSubscription from './types/pub/leaflet/graph/subscription' 91 + export * as PubLeafletInteractionsRecommend from './types/pub/leaflet/interactions/recommend' 90 92 export * as PubLeafletPagesCanvas from './types/pub/leaflet/pages/canvas' 91 93 export * as PubLeafletPagesLinearDocument from './types/pub/leaflet/pages/linearDocument' 92 94 export * as PubLeafletPollDefinition from './types/pub/leaflet/poll/definition' ··· 408 410 publication: PubLeafletPublicationRecord 409 411 blocks: PubLeafletBlocksNS 410 412 graph: PubLeafletGraphNS 413 + interactions: PubLeafletInteractionsNS 411 414 pages: PubLeafletPagesNS 412 415 poll: PubLeafletPollNS 413 416 richtext: PubLeafletRichtextNS ··· 417 420 this._client = client 418 421 this.blocks = new PubLeafletBlocksNS(client) 419 422 this.graph = new PubLeafletGraphNS(client) 423 + this.interactions = new PubLeafletInteractionsNS(client) 420 424 this.pages = new PubLeafletPagesNS(client) 421 425 this.poll = new PubLeafletPollNS(client) 422 426 this.richtext = new PubLeafletRichtextNS(client) ··· 523 527 'com.atproto.repo.deleteRecord', 524 528 undefined, 525 529 { collection: 'pub.leaflet.graph.subscription', ...params }, 530 + { headers }, 531 + ) 532 + } 533 + } 534 + 535 + export class PubLeafletInteractionsNS { 536 + _client: XrpcClient 537 + recommend: PubLeafletInteractionsRecommendRecord 538 + 539 + constructor(client: XrpcClient) { 540 + this._client = client 541 + this.recommend = new PubLeafletInteractionsRecommendRecord(client) 542 + } 543 + } 544 + 545 + export class PubLeafletInteractionsRecommendRecord { 546 + _client: XrpcClient 547 + 548 + constructor(client: XrpcClient) { 549 + this._client = client 550 + } 551 + 552 + async list( 553 + params: OmitKey<ComAtprotoRepoListRecords.QueryParams, 'collection'>, 554 + ): Promise<{ 555 + cursor?: string 556 + records: { uri: string; value: PubLeafletInteractionsRecommend.Record }[] 557 + }> { 558 + const res = await this._client.call('com.atproto.repo.listRecords', { 559 + collection: 'pub.leaflet.interactions.recommend', 560 + ...params, 561 + }) 562 + return res.data 563 + } 564 + 565 + async get( 566 + params: OmitKey<ComAtprotoRepoGetRecord.QueryParams, 'collection'>, 567 + ): Promise<{ 568 + uri: string 569 + cid: string 570 + value: PubLeafletInteractionsRecommend.Record 571 + }> { 572 + const res = await this._client.call('com.atproto.repo.getRecord', { 573 + collection: 'pub.leaflet.interactions.recommend', 574 + ...params, 575 + }) 576 + return res.data 577 + } 578 + 579 + async create( 580 + params: OmitKey< 581 + ComAtprotoRepoCreateRecord.InputSchema, 582 + 'collection' | 'record' 583 + >, 584 + record: Un$Typed<PubLeafletInteractionsRecommend.Record>, 585 + headers?: Record<string, string>, 586 + ): Promise<{ uri: string; cid: string }> { 587 + const collection = 'pub.leaflet.interactions.recommend' 588 + const res = await this._client.call( 589 + 'com.atproto.repo.createRecord', 590 + undefined, 591 + { collection, ...params, record: { ...record, $type: collection } }, 592 + { encoding: 'application/json', headers }, 593 + ) 594 + return res.data 595 + } 596 + 597 + async put( 598 + params: OmitKey< 599 + ComAtprotoRepoPutRecord.InputSchema, 600 + 'collection' | 'record' 601 + >, 602 + record: Un$Typed<PubLeafletInteractionsRecommend.Record>, 603 + headers?: Record<string, string>, 604 + ): Promise<{ uri: string; cid: string }> { 605 + const collection = 'pub.leaflet.interactions.recommend' 606 + const res = await this._client.call( 607 + 'com.atproto.repo.putRecord', 608 + undefined, 609 + { collection, ...params, record: { ...record, $type: collection } }, 610 + { encoding: 'application/json', headers }, 611 + ) 612 + return res.data 613 + } 614 + 615 + async delete( 616 + params: OmitKey<ComAtprotoRepoDeleteRecord.InputSchema, 'collection'>, 617 + headers?: Record<string, string>, 618 + ): Promise<void> { 619 + await this._client.call( 620 + 'com.atproto.repo.deleteRecord', 621 + undefined, 622 + { collection: 'pub.leaflet.interactions.recommend', ...params }, 526 623 { headers }, 527 624 ) 528 625 }
+26
lexicons/api/lexicons.ts
··· 1517 1517 }, 1518 1518 }, 1519 1519 }, 1520 + PubLeafletInteractionsRecommend: { 1521 + lexicon: 1, 1522 + id: 'pub.leaflet.interactions.recommend', 1523 + defs: { 1524 + main: { 1525 + type: 'record', 1526 + key: 'tid', 1527 + description: 'Record representing a recommend on a document', 1528 + record: { 1529 + type: 'object', 1530 + required: ['subject', 'createdAt'], 1531 + properties: { 1532 + subject: { 1533 + type: 'string', 1534 + format: 'at-uri', 1535 + }, 1536 + createdAt: { 1537 + type: 'string', 1538 + format: 'datetime', 1539 + }, 1540 + }, 1541 + }, 1542 + }, 1543 + }, 1544 + }, 1520 1545 PubLeafletPagesCanvas: { 1521 1546 lexicon: 1, 1522 1547 id: 'pub.leaflet.pages.canvas', ··· 2418 2443 PubLeafletContent: 'pub.leaflet.content', 2419 2444 PubLeafletDocument: 'pub.leaflet.document', 2420 2445 PubLeafletGraphSubscription: 'pub.leaflet.graph.subscription', 2446 + PubLeafletInteractionsRecommend: 'pub.leaflet.interactions.recommend', 2421 2447 PubLeafletPagesCanvas: 'pub.leaflet.pages.canvas', 2422 2448 PubLeafletPagesLinearDocument: 'pub.leaflet.pages.linearDocument', 2423 2449 PubLeafletPollDefinition: 'pub.leaflet.poll.definition',
+32
lexicons/api/types/pub/leaflet/interactions/recommend.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../lexicons' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'pub.leaflet.interactions.recommend' 16 + 17 + export interface Record { 18 + $type: 'pub.leaflet.interactions.recommend' 19 + subject: string 20 + createdAt: string 21 + [k: string]: unknown 22 + } 23 + 24 + const hashRecord = 'main' 25 + 26 + export function isRecord<V>(v: V) { 27 + return is$typed(v, id, hashRecord) 28 + } 29 + 30 + export function validateRecord<V>(v: V) { 31 + return validate<Record & V>(v, id, hashRecord, true) 32 + }
+2
lexicons/build.ts
··· 3 3 import { PubLeafletDocument } from "./src/document"; 4 4 import * as PublicationLexicons from "./src/publication"; 5 5 import * as PollLexicons from "./src/polls"; 6 + import * as InteractionsLexicons from "./src/interactions"; 6 7 import { ThemeLexicons } from "./src/theme"; 7 8 8 9 import * as fs from "fs"; ··· 31 32 ...BlockLexicons, 32 33 ...Object.values(PublicationLexicons), 33 34 ...Object.values(PollLexicons), 35 + ...Object.values(InteractionsLexicons), 34 36 ]; 35 37 36 38 // Write each lexicon to a file
+2 -1
lexicons/pub/leaflet/authFullPermissions.json
··· 21 21 "pub.leaflet.comment", 22 22 "pub.leaflet.poll.definition", 23 23 "pub.leaflet.poll.vote", 24 - "pub.leaflet.graph.subscription" 24 + "pub.leaflet.graph.subscription", 25 + "pub.leaflet.interactions.recommend" 25 26 ] 26 27 } 27 28 ]
+28
lexicons/pub/leaflet/interactions/recommend.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "pub.leaflet.interactions.recommend", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "description": "Record representing a recommend on a document", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "subject", 13 + "createdAt" 14 + ], 15 + "properties": { 16 + "subject": { 17 + "type": "string", 18 + "format": "at-uri" 19 + }, 20 + "createdAt": { 21 + "type": "string", 22 + "format": "datetime" 23 + } 24 + } 25 + } 26 + } 27 + } 28 + }
+2
lexicons/src/authFullPermissions.ts
··· 6 6 } from "./publication"; 7 7 import { PubLeafletComment } from "./comment"; 8 8 import { PubLeafletPollDefinition, PubLeafletPollVote } from "./polls"; 9 + import { PubLeafletInteractionsRecommend } from "./interactions"; 9 10 10 11 export const PubLeafletAuthFullPermissions: LexiconDoc = { 11 12 lexicon: 1, ··· 28 29 PubLeafletPollDefinition.id, 29 30 PubLeafletPollVote.id, 30 31 PubLeafletPublicationSubscription.id, 32 + PubLeafletInteractionsRecommend.id, 31 33 ], 32 34 }, 33 35 ],
+21
lexicons/src/interactions/index.ts
··· 1 + import { LexiconDoc } from "@atproto/lexicon"; 2 + 3 + export const PubLeafletInteractionsRecommend: LexiconDoc = { 4 + lexicon: 1, 5 + id: "pub.leaflet.interactions.recommend", 6 + defs: { 7 + main: { 8 + type: "record", 9 + key: "tid", 10 + description: "Record representing a recommend on a document", 11 + record: { 12 + type: "object", 13 + required: ["subject", "createdAt"], 14 + properties: { 15 + subject: { type: "string", format: "at-uri" }, 16 + createdAt: { type: "string", format: "datetime" }, 17 + }, 18 + }, 19 + }, 20 + }, 21 + };
+45
supabase/database.types.ts
··· 1075 1075 }, 1076 1076 ] 1077 1077 } 1078 + recommends_on_documents: { 1079 + Row: { 1080 + document: string 1081 + indexed_at: string 1082 + recommender_did: string 1083 + record: Json 1084 + uri: string 1085 + } 1086 + Insert: { 1087 + document: string 1088 + indexed_at?: string 1089 + recommender_did: string 1090 + record: Json 1091 + uri: string 1092 + } 1093 + Update: { 1094 + document?: string 1095 + indexed_at?: string 1096 + recommender_did?: string 1097 + record?: Json 1098 + uri?: string 1099 + } 1100 + Relationships: [ 1101 + { 1102 + foreignKeyName: "recommends_on_documents_document_fkey" 1103 + columns: ["document"] 1104 + isOneToOne: false 1105 + referencedRelation: "documents" 1106 + referencedColumns: ["uri"] 1107 + }, 1108 + { 1109 + foreignKeyName: "recommends_on_documents_recommender_did_fkey" 1110 + columns: ["recommender_did"] 1111 + isOneToOne: false 1112 + referencedRelation: "identities" 1113 + referencedColumns: ["atp_did"] 1114 + }, 1115 + ] 1116 + } 1078 1117 replicache_clients: { 1079 1118 Row: { 1080 1119 client_group: string ··· 1303 1342 Returns: { 1304 1343 like: unknown 1305 1344 }[] 1345 + } 1346 + parse_iso_timestamp: { 1347 + Args: { 1348 + "": string 1349 + } 1350 + Returns: string 1306 1351 } 1307 1352 pull_data: { 1308 1353 Args: {
+65
supabase/migrations/20260127000000_add_recommends_table.sql
··· 1 + create table "public"."recommends_on_documents" ( 2 + "uri" text not null, 3 + "record" jsonb not null, 4 + "document" text not null, 5 + "recommender_did" text not null, 6 + "indexed_at" timestamp with time zone not null default now() 7 + ); 8 + 9 + alter table "public"."recommends_on_documents" enable row level security; 10 + 11 + CREATE UNIQUE INDEX recommends_on_documents_pkey ON public.recommends_on_documents USING btree (uri); 12 + 13 + alter table "public"."recommends_on_documents" add constraint "recommends_on_documents_pkey" PRIMARY KEY using index "recommends_on_documents_pkey"; 14 + 15 + CREATE INDEX recommends_on_documents_document_idx ON public.recommends_on_documents USING btree (document); 16 + 17 + CREATE INDEX recommends_on_documents_recommender_did_idx ON public.recommends_on_documents USING btree (recommender_did); 18 + 19 + CREATE UNIQUE INDEX recommends_on_documents_recommender_document_idx ON public.recommends_on_documents USING btree (recommender_did, document); 20 + 21 + 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; 22 + 23 + 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; 24 + 25 + grant delete on table "public"."recommends_on_documents" to "anon"; 26 + 27 + grant insert on table "public"."recommends_on_documents" to "anon"; 28 + 29 + grant references on table "public"."recommends_on_documents" to "anon"; 30 + 31 + grant select on table "public"."recommends_on_documents" to "anon"; 32 + 33 + grant trigger on table "public"."recommends_on_documents" to "anon"; 34 + 35 + grant truncate on table "public"."recommends_on_documents" to "anon"; 36 + 37 + grant update on table "public"."recommends_on_documents" to "anon"; 38 + 39 + grant delete on table "public"."recommends_on_documents" to "authenticated"; 40 + 41 + grant insert on table "public"."recommends_on_documents" to "authenticated"; 42 + 43 + grant references on table "public"."recommends_on_documents" to "authenticated"; 44 + 45 + grant select on table "public"."recommends_on_documents" to "authenticated"; 46 + 47 + grant trigger on table "public"."recommends_on_documents" to "authenticated"; 48 + 49 + grant truncate on table "public"."recommends_on_documents" to "authenticated"; 50 + 51 + grant update on table "public"."recommends_on_documents" to "authenticated"; 52 + 53 + grant delete on table "public"."recommends_on_documents" to "service_role"; 54 + 55 + grant insert on table "public"."recommends_on_documents" to "service_role"; 56 + 57 + grant references on table "public"."recommends_on_documents" to "service_role"; 58 + 59 + grant select on table "public"."recommends_on_documents" to "service_role"; 60 + 61 + grant trigger on table "public"."recommends_on_documents" to "service_role"; 62 + 63 + grant truncate on table "public"."recommends_on_documents" to "service_role"; 64 + 65 + grant update on table "public"."recommends_on_documents" to "service_role";