an atproto based link aggregator
1/**
2 * Shared comment query functions.
3 * Centralizes comment fetching logic to avoid duplication.
4 */
5
6import { contentDb } from '$lib/server/db';
7import { posts, comments } from '$lib/server/db/schema';
8import { desc, eq, and, type SQL } from 'drizzle-orm';
9import type { CommentBase } from '../enrichment';
10
11export const DEFAULT_COMMENTS_LIMIT = 50;
12
13/** Comment with post context for display in listings */
14export interface CommentWithPostContext extends CommentBase {
15 postRkey: string;
16 postTitle: string;
17}
18
19/** Options for fetching comments */
20export interface GetCommentsOptions {
21 /** Number of comments to fetch (default: 50) */
22 limit?: number;
23 /** Additional WHERE clause for comments table */
24 where?: SQL;
25 /** Include hidden comments (default: false, set true for admin views) */
26 includeHidden?: boolean;
27 /** Filter to comments by this author DID */
28 authorDid?: string;
29 /** Filter to comments on this post URI */
30 postUri?: string;
31}
32
33/**
34 * Fetch recent comments with post context.
35 * Hidden comments and comments on hidden posts are excluded by default.
36 */
37export async function getCommentsWithPostContext(
38 options: GetCommentsOptions = {}
39): Promise<CommentWithPostContext[]> {
40 const {
41 limit = DEFAULT_COMMENTS_LIMIT,
42 where,
43 includeHidden = false,
44 authorDid,
45 postUri
46 } = options;
47
48 // Build where conditions
49 const conditions: SQL[] = [];
50
51 if (!includeHidden) {
52 conditions.push(eq(comments.isHidden, 0));
53 conditions.push(eq(posts.isHidden, 0));
54 }
55
56 if (authorDid) {
57 conditions.push(eq(comments.authorDid, authorDid));
58 }
59
60 if (postUri) {
61 conditions.push(eq(comments.postUri, postUri));
62 }
63
64 if (where) {
65 conditions.push(where);
66 }
67
68 const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
69
70 let query = contentDb
71 .select({
72 uri: comments.uri,
73 cid: comments.cid,
74 authorDid: comments.authorDid,
75 rkey: comments.rkey,
76 postUri: comments.postUri,
77 parentUri: comments.parentUri,
78 text: comments.text,
79 createdAt: comments.createdAt,
80 indexedAt: comments.indexedAt,
81 postRkey: posts.rkey,
82 postTitle: posts.title
83 })
84 .from(comments)
85 .innerJoin(posts, eq(comments.postUri, posts.uri))
86 .orderBy(desc(comments.createdAt))
87 .limit(limit);
88
89 if (whereClause) {
90 query = query.where(whereClause) as typeof query;
91 }
92
93 return query;
94}
95
96/**
97 * Fetch comments for a specific post.
98 * Hidden comments are excluded unless includeHidden is true.
99 * Note: Does not check if the post itself is hidden - caller should verify.
100 */
101export async function getCommentsForPost(
102 postUri: string,
103 options: { includeHidden?: boolean } = {}
104): Promise<CommentBase[]> {
105 const { includeHidden = false } = options;
106
107 const whereClause = includeHidden
108 ? eq(comments.postUri, postUri)
109 : and(eq(comments.postUri, postUri), eq(comments.isHidden, 0));
110
111 return contentDb
112 .select({
113 uri: comments.uri,
114 cid: comments.cid,
115 authorDid: comments.authorDid,
116 rkey: comments.rkey,
117 postUri: comments.postUri,
118 parentUri: comments.parentUri,
119 text: comments.text,
120 createdAt: comments.createdAt,
121 indexedAt: comments.indexedAt
122 })
123 .from(comments)
124 .where(whereClause);
125}