diff --git a/packages/frontpage/app/(app)/moderation/_components/report-card.tsx b/packages/frontpage/app/(app)/moderation/_components/report-card.tsx
index 756e196f..de8906ee 100644
--- a/packages/frontpage/app/(app)/moderation/_components/report-card.tsx
+++ b/packages/frontpage/app/(app)/moderation/_components/report-card.tsx
@@ -11,7 +11,6 @@ import { performModerationAction } from "../page";
import { UserHandle } from "./user-handle";
import Link from "next/link";
import { cn } from "@/lib/utils";
-import { CommentCollection } from "@/lib/data/atproto/comment";
import { getPostFromComment } from "@/lib/data/db/post";
import { getCommentLink, getPostLink } from "@/lib/navigation";
import { nsids } from "@/lib/data/atproto/repo";
@@ -25,7 +24,7 @@ const createLink = async (
case nsids.FyiUnravelFrontpagePost:
return getPostLink({ handleOrDid: author!, rkey: rkey! });
- case CommentCollection: {
+ case nsids.FyiUnravelFrontpageComment: {
const { postAuthor, postRkey } = (await getPostFromComment({
rkey: rkey!,
did: author!,
diff --git a/packages/frontpage/app/(app)/moderation/page.tsx b/packages/frontpage/app/(app)/moderation/page.tsx
index 167acd79..487469d6 100644
--- a/packages/frontpage/app/(app)/moderation/page.tsx
+++ b/packages/frontpage/app/(app)/moderation/page.tsx
@@ -17,7 +17,6 @@ import {
type ModerationEventDTO,
createModerationEvent,
} from "@/lib/data/db/moderation";
-import { CommentCollection } from "@/lib/data/atproto/comment";
import { revalidatePath } from "next/cache";
import Link from "next/link";
import { ReportCard } from "./_components/report-card";
@@ -51,8 +50,8 @@ export async function performModerationAction(
if (report.subjectCollection) {
if (report.subjectCollection === nsids.FyiUnravelFrontpagePost) {
newModEvent.subjectCollection = nsids.FyiUnravelFrontpagePost;
- } else if (report.subjectCollection === CommentCollection) {
- newModEvent.subjectCollection = CommentCollection;
+ } else if (report.subjectCollection === nsids.FyiUnravelFrontpageComment) {
+ newModEvent.subjectCollection = nsids.FyiUnravelFrontpageComment;
}
newModEvent.subjectRkey = report.subjectRkey;
@@ -69,7 +68,7 @@ export async function performModerationAction(
hide: input.status === "accepted",
});
- case CommentCollection:
+ case nsids.FyiUnravelFrontpageComment:
return await moderateComment({
rkey: report.subjectRkey!,
authorDid: report.subjectDid as DID,
diff --git a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx
index e820af78..52fdf671 100644
--- a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx
+++ b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/[commentAuthor]/[commentRkey]/page.tsx
@@ -4,7 +4,7 @@ import { type Metadata } from "next";
import { getVerifiedHandle } from "@/lib/data/atproto/identity";
import { type CommentPageParams, getCommentPageData } from "./_lib/page-data";
import { LinkAlternateAtUri } from "@/lib/components/link-alternate-at";
-import { CommentCollection } from "@/lib/data/atproto/comment";
+import { nsids } from "@/lib/data/atproto/repo";
function truncateText(text: string, maxLength: number) {
if (text.length > maxLength) {
@@ -61,7 +61,7 @@ export default async function CommentPage(props: {
<>
diff --git a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/actions.tsx b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/actions.tsx
index 3d43a59c..83bbee6b 100644
--- a/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/actions.tsx
+++ b/packages/frontpage/app/(app)/post/[postAuthor]/[postRkey]/_lib/actions.tsx
@@ -1,6 +1,5 @@
"use server";
-import { CommentCollection } from "@/lib/data/atproto/comment";
import { type DID } from "@/lib/data/atproto/did";
import { getComment } from "@/lib/data/db/comment";
import { getPost } from "@/lib/data/db/post";
@@ -12,6 +11,7 @@ import { revalidatePath } from "next/cache";
import { createComment, deleteComment } from "@/lib/api/comment";
import { createVote, deleteVote } from "@/lib/api/vote";
import { invariant } from "@/lib/utils";
+import { nsids } from "@/lib/data/atproto/repo";
export async function createCommentAction(
input: { parentRkey?: string; postRkey: string; postAuthorDid: DID },
@@ -76,9 +76,9 @@ export async function reportCommentAction(
await createReport({
...formResult.data,
- subjectUri: `at://${input.authorDid}/${CommentCollection}/${input.rkey}`,
+ subjectUri: `at://${input.authorDid}/${nsids.FyiUnravelFrontpageComment}/${input.rkey}`,
subjectDid: input.authorDid,
- subjectCollection: CommentCollection,
+ subjectCollection: nsids.FyiUnravelFrontpageComment,
subjectRkey: input.rkey,
subjectCid: input.cid,
});
@@ -96,7 +96,7 @@ export async function commentVoteAction(input: {
rkey: input.rkey,
cid: input.cid,
authorDid: input.authorDid,
- collection: CommentCollection,
+ collection: nsids.FyiUnravelFrontpageComment,
},
});
}
diff --git a/packages/frontpage/app/api/receive_hook/handlers.ts b/packages/frontpage/app/api/receive_hook/handlers.ts
index 0cd7e0be..b4533840 100644
--- a/packages/frontpage/app/api/receive_hook/handlers.ts
+++ b/packages/frontpage/app/api/receive_hook/handlers.ts
@@ -1,7 +1,7 @@
-import * as atprotoComment from "@/lib/data/atproto/comment";
import { getPdsUrl, type DID } from "@/lib/data/atproto/did";
import { type Operation } from "@/lib/data/atproto/event";
import { getAtprotoClient, nsids } from "@/lib/data/atproto/repo";
+import { AtUri } from "@/lib/data/atproto/uri";
import * as atprotoVote from "@/lib/data/atproto/vote";
import * as dbComment from "@/lib/data/db/comment";
import * as dbNotification from "@/lib/data/db/notification";
@@ -22,12 +22,17 @@ type HandlerInput = {
// Since we use read after write, we need to check if the record exists before creating it
// If it's a delete then setting the status to delete again doesn't matter
-export async function handlePost({ op, repo, rkey }: HandlerInput) {
+async function getAtprotoClientFromRepo(repo: DID) {
const pds = await getPdsUrl(repo);
if (!pds) {
throw new Error("Failed to get PDS");
}
- const atproto = getAtprotoClient(pds);
+ return getAtprotoClient(pds);
+}
+
+export async function handlePost({ op, repo, rkey }: HandlerInput) {
+ const atproto = await getAtprotoClientFromRepo(repo);
+
if (op.action === "create") {
const postRecord = await atproto.fyi.unravel.frontpage.post.get({
repo,
@@ -91,8 +96,10 @@ export async function handlePost({ op, repo, rkey }: HandlerInput) {
}
export async function handleComment({ op, repo, rkey }: HandlerInput) {
+ const atproto = await getAtprotoClientFromRepo(repo);
+
if (op.action === "create") {
- const commentRecord = await atprotoComment.getComment({
+ const commentRecord = await atproto.fyi.unravel.frontpage.comment.get({
rkey,
repo,
});
@@ -109,22 +116,24 @@ export async function handleComment({ op, repo, rkey }: HandlerInput) {
});
} else {
const { content, createdAt, parent, post } = commentRecord.value;
+ const postUri = AtUri.parse(post.uri);
+ const parentUri = parent ? AtUri.parse(parent.uri) : null;
const createdComment = await dbComment.createComment({
cid: commentRecord.cid,
authorDid: repo,
rkey,
content,
createdAt: new Date(createdAt),
- parent: parent
+ parent: parentUri
? {
//TODO: is authority a DID?
- authorDid: parent.uri.authority as DID,
- rkey: parent.uri.rkey,
+ authorDid: parentUri.authority as DID,
+ rkey: parentUri.rkey,
}
: undefined,
post: {
- authorDid: post.uri.authority as DID,
- rkey: post.uri.rkey,
+ authorDid: postUri.authority as DID,
+ rkey: postUri.rkey,
},
status: "live",
});
@@ -133,7 +142,7 @@ export async function handleComment({ op, repo, rkey }: HandlerInput) {
throw new Error("Failed to insert comment from relay in database");
}
- const didToNotify = parent ? parent.uri.authority : post.uri.authority;
+ const didToNotify = parentUri ? parentUri.authority : postUri.authority;
if (didToNotify !== repo) {
await dbNotification.createNotification({
@@ -190,7 +199,7 @@ export async function handleVote({ op, repo, rkey }: HandlerInput) {
}
break;
}
- case atprotoComment.CommentCollection: {
+ case nsids.FyiUnravelFrontpageComment: {
const commentVote = await dbVote.uncached_doesCommentVoteExist(
repo,
rkey,
diff --git a/packages/frontpage/app/api/receive_hook/route.ts b/packages/frontpage/app/api/receive_hook/route.ts
index 3e8751ce..923f3c3e 100644
--- a/packages/frontpage/app/api/receive_hook/route.ts
+++ b/packages/frontpage/app/api/receive_hook/route.ts
@@ -1,7 +1,6 @@
import { db } from "@/lib/db";
import * as schema from "@/lib/schema";
import { Commit } from "@/lib/data/atproto/event";
-import * as atprotoComment from "@/lib/data/atproto/comment";
import * as atprotoVote from "@/lib/data/atproto/vote";
import { getPdsUrl } from "@/lib/data/atproto/did";
import { handleComment, handlePost, handleVote } from "./handlers";
@@ -46,7 +45,7 @@ export async function POST(request: Request) {
case nsids.FyiUnravelFrontpagePost:
await handlePost({ op, repo, rkey });
break;
- case atprotoComment.CommentCollection:
+ case nsids.FyiUnravelFrontpageComment:
await handleComment({ op, repo, rkey });
break;
case atprotoVote.VoteCollection:
diff --git a/packages/frontpage/lib/api/comment.ts b/packages/frontpage/lib/api/comment.ts
index c286d5f4..7d0cb6cc 100644
--- a/packages/frontpage/lib/api/comment.ts
+++ b/packages/frontpage/lib/api/comment.ts
@@ -1,5 +1,4 @@
import "server-only";
-import * as atproto from "../data/atproto/comment";
import { DataLayerError } from "../data/error";
import { ensureUser } from "../data/user";
import * as db from "../data/db/comment";
@@ -8,8 +7,13 @@ import { createNotification } from "../data/db/notification";
import { invariant } from "../utils";
import { TID } from "@atproto/common-web";
import { after } from "next/server";
+import { getAtprotoClient, nsids } from "../data/atproto/repo";
-export type ApiCreateCommentInput = Omit
& {
+export type ApiCreateCommentInput = {
+ // TODO: Use strongRef type for parent and post
+ parent?: { cid: string; rkey: string; authorDid: DID };
+ post: { cid: string; rkey: string; authorDid: DID };
+ content: string;
authorDid: DID;
};
@@ -38,12 +42,26 @@ export async function createComment({
invariant(dbCreatedComment, "Failed to insert comment in database");
after(() =>
- atproto.createComment({
- parent,
- post,
- content: sanitizedContent,
- rkey,
- }),
+ getAtprotoClient().fyi.unravel.frontpage.comment.create(
+ {
+ repo: user.did,
+ rkey,
+ },
+ {
+ parent: parent
+ ? {
+ cid: parent.cid,
+ uri: `at://${parent.authorDid}/${nsids.FyiUnravelFrontpageComment}/${parent.rkey}`,
+ }
+ : undefined,
+ post: {
+ cid: post.cid,
+ uri: `at://${post.authorDid}/${nsids.FyiUnravelFrontpagePost}/${post.rkey}`,
+ },
+ content: sanitizedContent,
+ createdAt: new Date().toISOString(),
+ },
+ ),
);
const didToNotify = parent ? parent.authorDid : post.authorDid;
@@ -73,7 +91,12 @@ export async function deleteComment({
}
try {
- after(() => atproto.deleteComment(authorDid, rkey));
+ after(() =>
+ getAtprotoClient().fyi.unravel.frontpage.comment.delete({
+ repo: authorDid,
+ rkey,
+ }),
+ );
await db.deleteComment({ authorDid: user.did, rkey });
} catch (e) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
diff --git a/packages/frontpage/lib/api/vote.ts b/packages/frontpage/lib/api/vote.ts
index ee3ef25d..25fba022 100644
--- a/packages/frontpage/lib/api/vote.ts
+++ b/packages/frontpage/lib/api/vote.ts
@@ -4,7 +4,6 @@ import * as atproto from "../data/atproto/vote";
import { DataLayerError } from "../data/error";
import { ensureUser } from "../data/user";
import { type DID } from "../data/atproto/did";
-import { CommentCollection } from "../data/atproto/comment";
import { invariant } from "../utils";
import { TID } from "@atproto/common-web";
import { after } from "next/server";
@@ -16,7 +15,9 @@ export type ApiCreateVoteInput = {
rkey: string;
cid: string;
authorDid: DID;
- collection: typeof nsids.FyiUnravelFrontpagePost | typeof CommentCollection;
+ collection:
+ | typeof nsids.FyiUnravelFrontpagePost
+ | typeof nsids.FyiUnravelFrontpageComment;
};
};
@@ -42,7 +43,7 @@ export async function createVote({ authorDid, subject }: ApiCreateVoteInput) {
});
invariant(dbCreatedVote, "Failed to insert post vote in database");
- } else if (subject.collection == CommentCollection) {
+ } else if (subject.collection == nsids.FyiUnravelFrontpageComment) {
const dbCreatedVote = await db.createCommentVote({
repo: authorDid,
rkey,
diff --git a/packages/frontpage/lib/data/atproto/comment.ts b/packages/frontpage/lib/data/atproto/comment.ts
deleted file mode 100644
index 92f4976a..00000000
--- a/packages/frontpage/lib/data/atproto/comment.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import "server-only";
-import {
- atprotoCreateRecord,
- atprotoDeleteRecord,
- atprotoGetRecord,
-} from "./record";
-import { createAtUriParser } from "./uri";
-import { DataLayerError } from "../error";
-import { z } from "zod";
-import { type DID, getPdsUrl } from "./did";
-import { MAX_COMMENT_LENGTH } from "../db/constants";
-import { nsids } from "./repo";
-
-export const CommentCollection = "fyi.unravel.frontpage.comment";
-
-export const CommentRecord = z.object({
- content: z.string().max(MAX_COMMENT_LENGTH),
- parent: z
- .object({
- cid: z.string(),
- uri: createAtUriParser(z.literal(CommentCollection)),
- })
- .optional(),
- post: z.object({
- cid: z.string(),
- uri: createAtUriParser(z.literal(nsids.FyiUnravelFrontpagePost)),
- }),
- createdAt: z.string(),
-});
-
-export type Comment = z.infer;
-
-export type CommentInput = {
- parent?: { cid: string; rkey: string; authorDid: DID };
- post: { cid: string; rkey: string; authorDid: DID };
- content: string;
- rkey: string;
-};
-
-export async function createComment({
- parent,
- post,
- content,
- rkey,
-}: CommentInput) {
- // Collapse newlines into a single \n\n and trim whitespace
- const record = {
- content,
- parent: parent
- ? {
- cid: parent.cid,
- uri: `at://${parent.authorDid}/${CommentCollection}/${parent.rkey}`,
- }
- : undefined,
- post: {
- cid: post.cid,
- uri: `at://${post.authorDid}/${nsids.FyiUnravelFrontpagePost}/${post.rkey}`,
- },
- createdAt: new Date().toISOString(),
- };
-
- const parseResult = CommentRecord.safeParse(record);
- if (!parseResult.success) {
- throw new DataLayerError("Invalid comment record", {
- cause: parseResult.error,
- });
- }
-
- const result = await atprotoCreateRecord({
- record,
- collection: CommentCollection,
- rkey,
- });
-
- return {
- rkey: result.uri.rkey,
- cid: result.cid,
- };
-}
-
-export async function deleteComment(authorDid: DID, rkey: string) {
- await atprotoDeleteRecord({
- authorDid,
- rkey,
- collection: CommentCollection,
- });
-}
-
-export async function getComment({ rkey, repo }: { rkey: string; repo: DID }) {
- const service = await getPdsUrl(repo);
-
- if (!service) {
- throw new DataLayerError("Failed to get service url");
- }
-
- const { value, cid } = await atprotoGetRecord({
- serviceEndpoint: service,
- repo,
- collection: CommentCollection,
- rkey,
- });
-
- return { value: CommentRecord.parse(value), cid };
-}
diff --git a/packages/frontpage/lib/data/atproto/event.ts b/packages/frontpage/lib/data/atproto/event.ts
index 79e7c9cb..746a958b 100644
--- a/packages/frontpage/lib/data/atproto/event.ts
+++ b/packages/frontpage/lib/data/atproto/event.ts
@@ -1,6 +1,5 @@
import "server-only";
import { z } from "zod";
-import { CommentCollection } from "./comment";
import { isDid } from "./did";
import { nsids } from "./repo";
@@ -8,7 +7,7 @@ import { nsids } from "./repo";
export const Collection = z.union([
z.literal(nsids.FyiUnravelFrontpagePost),
- z.literal(CommentCollection),
+ z.literal(nsids.FyiUnravelFrontpageComment),
z.literal("fyi.unravel.frontpage.vote"),
]);
diff --git a/packages/frontpage/lib/data/atproto/vote.ts b/packages/frontpage/lib/data/atproto/vote.ts
index 622064c5..22ad5baa 100644
--- a/packages/frontpage/lib/data/atproto/vote.ts
+++ b/packages/frontpage/lib/data/atproto/vote.ts
@@ -5,7 +5,6 @@ import {
atprotoGetRecord,
} from "./record";
import { z } from "zod";
-import { CommentCollection } from "./comment";
import { type DID, getPdsUrl } from "./did";
import { createAtUriParser } from "./uri";
import { DataLayerError } from "../error";
@@ -15,7 +14,7 @@ export const VoteCollection = "fyi.unravel.frontpage.vote";
const VoteSubjectCollection = z.union([
z.literal(nsids.FyiUnravelFrontpagePost),
- z.literal(CommentCollection),
+ z.literal(nsids.FyiUnravelFrontpageComment),
]);
export const VoteRecord = z.object({
@@ -34,7 +33,9 @@ export type VoteInput = {
rkey: string;
cid: string;
authorDid: DID;
- collection: typeof nsids.FyiUnravelFrontpagePost | typeof CommentCollection;
+ collection:
+ | typeof nsids.FyiUnravelFrontpagePost
+ | typeof nsids.FyiUnravelFrontpageComment;
};
};
diff --git a/packages/frontpage/lib/data/db/shared.ts b/packages/frontpage/lib/data/db/shared.ts
index f414cc2a..4783bbe2 100644
--- a/packages/frontpage/lib/data/db/shared.ts
+++ b/packages/frontpage/lib/data/db/shared.ts
@@ -1,5 +1,4 @@
import { headers } from "next/headers";
-import { CommentCollection } from "../atproto/comment";
import { type DID } from "../atproto/did";
import { getPostFromComment } from "./post";
import { nsids } from "../atproto/repo";
@@ -24,7 +23,7 @@ export const createFrontPageLink = async (
case nsids.FyiUnravelFrontpagePost:
return `/post/${author}/${rkey}/`;
- case CommentCollection: {
+ case nsids.FyiUnravelFrontpageComment: {
const { postAuthor, postRkey } = (await getPostFromComment({
rkey: rkey!,
did: author,