From 24c7eb2470e35698d8ccfd9fa9b80dba1cb08ee7 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Mon, 27 Jul 2026 12:45:05 +0100 Subject: [PATCH] web/api: add comment and issue crud ops --- web/src/app.css | 2 ++ web/src/lib/api/comment.ts | 37 ++++++++++++++++++++++ web/src/lib/api/issue.ts | 37 ++++++++++++++++++++++ web/src/lib/api/records.ts | 64 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 web/src/lib/api/comment.ts create mode 100644 web/src/lib/api/issue.ts diff --git a/web/src/app.css b/web/src/app.css index dd2874fc..934bc84f 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -66,6 +66,7 @@ --color-background-info-subtle: #eef2ff; --color-background-info-subtle-hover: #e0e7ff; --color-background-info-emphasis: #4f46e5; + --color-background-neutral-emphasis: #1f2937; /* --- foreground --- */ --color-foreground-default: #111827; @@ -251,6 +252,7 @@ --color-background-info-subtle: #312e81; --color-background-info-subtle-hover: #3730a3; --color-background-info-emphasis: #4f46e5; + --color-background-neutral-emphasis: #374151; /* --- foreground (Dark) --- */ --color-foreground-default: #f3f4f6; diff --git a/web/src/lib/api/comment.ts b/web/src/lib/api/comment.ts new file mode 100644 index 00000000..ea647d81 --- /dev/null +++ b/web/src/lib/api/comment.ts @@ -0,0 +1,37 @@ +import { ok } from "@atcute/client"; +import { mainSchema as putRecordSchema } from "@atcute/atproto/types/repo/putRecord"; +import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord"; +import type { Nsid, RecordKey } from "@atcute/lexicons/syntax"; +import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; +import { createClient } from "$lib/auth/agent"; +import type { CommentRecord, RecordView } from "./records"; + +const COMMENT_COLLECTION = "sh.tangled.feed.comment" as Nsid; + +export const putComment = async ( + agent: OAuthUserAgent, + rkey: string, + record: CommentRecord +): Promise> => { + const rpc = createClient(agent); + const { uri, cid } = await ok( + rpc.call(putRecordSchema, { + input: { + repo: agent.sub, + collection: COMMENT_COLLECTION, + rkey: rkey as RecordKey, + record + } + }) + ); + return { uri, cid, value: record }; +}; + +export const deleteComment = async (agent: OAuthUserAgent, rkey: string): Promise => { + const rpc = createClient(agent); + await ok( + rpc.call(deleteRecordSchema, { + input: { repo: agent.sub, collection: COMMENT_COLLECTION, rkey: rkey as RecordKey } + }) + ); +}; diff --git a/web/src/lib/api/issue.ts b/web/src/lib/api/issue.ts new file mode 100644 index 00000000..21db8cc1 --- /dev/null +++ b/web/src/lib/api/issue.ts @@ -0,0 +1,37 @@ +import { ok } from "@atcute/client"; +import { mainSchema as putRecordSchema } from "@atcute/atproto/types/repo/putRecord"; +import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord"; +import type { Nsid, RecordKey } from "@atcute/lexicons/syntax"; +import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; +import { createClient } from "$lib/auth/agent"; +import type { IssueRecord, RecordView } from "./records"; + +const ISSUE_COLLECTION = "sh.tangled.repo.issue" as Nsid; + +export const putIssue = async ( + agent: OAuthUserAgent, + rkey: string, + record: IssueRecord +): Promise> => { + const rpc = createClient(agent); + const { uri, cid } = await ok( + rpc.call(putRecordSchema, { + input: { + repo: agent.sub, + collection: ISSUE_COLLECTION, + rkey: rkey as RecordKey, + record + } + }) + ); + return { uri, cid, value: record }; +}; + +export const deleteIssue = async (agent: OAuthUserAgent, rkey: string): Promise => { + const rpc = createClient(agent); + await ok( + rpc.call(deleteRecordSchema, { + input: { repo: agent.sub, collection: ISSUE_COLLECTION, rkey: rkey as RecordKey } + }) + ); +}; diff --git a/web/src/lib/api/records.ts b/web/src/lib/api/records.ts index 7adf8a5c..cb9906fa 100644 --- a/web/src/lib/api/records.ts +++ b/web/src/lib/api/records.ts @@ -1,8 +1,10 @@ import type { BobbinContext, XrpcRequestInit } from "./client"; import { jsonGet } from "./_request"; import type * as ShTangledActorProfile from "./lexicons/types/sh/tangled/actor/profile"; +import type * as ShTangledFeedComment from "./lexicons/types/sh/tangled/feed/comment"; import type * as ShTangledRepo from "./lexicons/types/sh/tangled/repo"; import type * as ShTangledRepoIssue from "./lexicons/types/sh/tangled/repo/issue"; +import type * as ShTangledRepoIssueState from "./lexicons/types/sh/tangled/repo/issue/state"; import type * as ShTangledRepoPull from "./lexicons/types/sh/tangled/repo/pull"; import type * as ShTangledString from "./lexicons/types/sh/tangled/string"; @@ -19,8 +21,10 @@ export interface RecordList { export type RepoRecord = ShTangledRepo.Main; export type ProfileRecord = ShTangledActorProfile.Main; export type IssueRecord = ShTangledRepoIssue.Main; +export type IssueStateRecord = ShTangledRepoIssueState.Main; export type PullRecord = ShTangledRepoPull.Main; export type StringRecord = ShTangledString.Main; +export type CommentRecord = ShTangledFeedComment.Main; export const BULK_LIMIT = 50; @@ -48,6 +52,66 @@ export const getProfile = (ctx: BobbinContext, did: string, init?: XrpcRequestIn export const getIssue = (ctx: BobbinContext, issue: string, init?: XrpcRequestInit) => jsonGet>(ctx, "sh.tangled.repo.getIssue", { issue }, init); +export type IssueState = "open" | "closed"; + +// a listIssues row: the embedded issue record plus bobbin-derived state/counts. +export interface IssueListItem { + cid?: string; + commentCount: number; + state: string; + stateUpdatedAt?: string; + uri: string; + value: IssueRecord; +} + +export interface IssueListPage { + cursor?: string | null; + items: IssueListItem[]; +} + +export interface ListIssuesFilter { + state?: IssueState; + author?: string; + limit?: number; + cursor?: string; + order?: "asc" | "desc"; +} + +export const listIssues = ( + ctx: BobbinContext, + subject: string, + filter: ListIssuesFilter = {}, + init?: XrpcRequestInit +) => jsonGet(ctx, "sh.tangled.repo.listIssues", { subject, ...filter }, init); + +// getIssue returns the raw record only; the derived open/closed state lives in +// separate sh.tangled.repo.issue.state records, latest-wins. +export interface IssueStateList { + cursor?: string | null; + items: RecordView[]; +} + +export const listIssueStates = ( + ctx: BobbinContext, + subject: string, + filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, + init?: XrpcRequestInit +) => jsonGet(ctx, "sh.tangled.repo.issue.listStates", { subject, ...filter }, init); + +// comments on any record (issue/pull/string) are flat sh.tangled.feed.comment records; +// threading is derived client-side from each comment's optional replyTo strongRef. +export interface CommentListPage { + cursor?: string | null; + items: RecordView[]; +} + +export const listComments = ( + ctx: BobbinContext, + subject: string, + filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, + init?: XrpcRequestInit +) => jsonGet(ctx, "sh.tangled.feed.listComments", { subject, ...filter }, init); + export const getPull = (ctx: BobbinContext, pull: string, init?: XrpcRequestInit) => jsonGet>(ctx, "sh.tangled.repo.getPull", { pull }, init); -- 2.51.2