Files
shd.build/src/routes/agents/[id]/+page.server.ts
T
JB a434cbe60c
Validate and deploy production / validate (push) Successful in 26s
Validate and deploy staging / validate (push) Successful in 25s
Validate and deploy production / deploy (push) Failing after 1m10s
Validate and deploy staging / deploy (push) Failing after 8s
feat: expand profiles and weapon configuration
2026-07-28 00:11:40 -04:00

668 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { avatarUrl } from '$lib/avatar';
import { getSessionUser } from '$lib/server/auth/session';
import { getDatabase } from '$lib/server/database';
import { normalizeUserMarkdown, renderUserMarkdown } from '$lib/markdown';
import { createNotification } from '$lib/server/notifications';
import {
getCommunityModerationPolicy,
moderateCommunityText,
createUserContentReport,
recordCommunityModerationScan
} from '$lib/server/community-moderation';
import { error, fail, redirect } from '@sveltejs/kit';
import { randomUUID } from 'node:crypto';
import { queueCurrentAvatarReview } from '$lib/server/avatars';
import { agentProfileHref } from '$lib/agents';
import { resolveAgentProfile } from '$lib/server/agent-profiles';
import {
orderProfileCommentThread,
profileCommentNotificationRecipient
} from '$lib/server/profile-comment-thread';
import {
notifyProfileCommentFollowers,
profileCommentConversation
} from '$lib/server/profile-comment-follows';
import { loadAccountControlData } from '$lib/server/account-control-data';
import { saveProfileBio, updateProfileVisibility } from '$lib/server/profile-settings';
import type { Actions, PageServerLoad } from './$types';
const COMMENT_MAX_CHARACTERS = 2_000;
const RAPID_COMMENT_SECONDS = 10;
const unavailable = (row: Record<string, unknown> | null | undefined) => {
const banned = typeof row?.banned_until === 'string' && new Date(row.banned_until) > new Date();
return !row || Boolean(row.deleted_at || row.blocked_at || banned);
};
const commentHref = (
targetType: string,
slug: unknown,
targetId: string,
targetPublicSlug: unknown,
commentId: string
) => {
if (targetType === 'profile') {
return agentProfileHref({
id: targetId,
publicSlug: typeof targetPublicSlug === 'string' ? targetPublicSlug : null
}, `profile-comment-${commentId}`);
}
return `/builds/${encodeURIComponent(String(slug ?? ''))}#comment-${encodeURIComponent(commentId)}`;
};
export const load: PageServerLoad = async ({ params, cookies, url }) => {
const database = await getDatabase();
const viewer = await getSessionUser(cookies);
const resolved = await resolveAgentProfile(database, params.id);
if (!resolved) error(404, 'Agent profile not found');
const agentId = resolved.id;
const agent = await (await database.prepare(`SELECT users.id,
COALESCE(profile.display_name,users.username) AS display_name,
profile.public_slug,profile.bio_markdown,profile.is_public,profile.bio_updated_at,
COALESCE(moderation.bio_hidden,0) AS bio_hidden,moderation.reason AS moderation_reason,
avatar.source AS avatar_source,avatar.value AS avatar_value,avatar.updated_at AS avatar_updated_at,
status.blocked_at,status.banned_until,deleted.deleted_at
FROM users
LEFT JOIN user_profiles profile ON profile.user_id=users.id
LEFT JOIN profile_moderation moderation ON moderation.user_id=users.id
LEFT JOIN user_avatars avatar ON avatar.user_id=users.id
LEFT JOIN user_status status ON status.user_id=users.id
LEFT JOIN deleted_accounts deleted ON deleted.user_id=users.id
WHERE users.id=?`)).get(agentId);
const isSelf = viewer?.id === agentId;
if (
unavailable(agent)
|| (!Boolean(agent?.is_public) && !isSelf)
) error(404, 'Agent profile not found');
if (params.id !== String(agent.public_slug)) {
redirect(308, agentProfileHref({ id: agentId, publicSlug: String(agent.public_slug) }));
}
const [pendingBioRevision, accountControl] = isSelf
? await Promise.all([
(await database.prepare(`SELECT revision.body_markdown,revision.created_at,scan.reason
FROM moderated_content_revisions revision
JOIN content_moderation_scans scan ON scan.revision_id=revision.id
LEFT JOIN content_moderation_reviews review ON review.scan_id=scan.id
WHERE revision.content_type='profile_bio' AND revision.content_id=?
AND scan.status IN ('review','priority_review','error') AND review.id IS NULL
ORDER BY revision.created_at DESC LIMIT 1`)).get(agentId),
loadAccountControlData(database, agentId, url.searchParams.get('control'))
])
: [null, null];
const [roleRows, buildRows, guideRows, authoredCommentRows, profileCommentRows, authoredCount, profileCommentCount, contentCounts] =
await Promise.all([
(await database.prepare(`SELECT roles.name FROM user_roles
JOIN roles ON roles.id=user_roles.role_id
WHERE user_roles.user_id=?
ORDER BY roles.name COLLATE NOCASE,roles.id`)).all(agentId),
(await database.prepare(`SELECT id,slug,title,COALESCE(summary,'') AS summary,updated_at
FROM builds WHERE owner_id=? AND visibility='public'
AND (guide_body IS NULL OR length(trim(guide_body))=0)
ORDER BY updated_at DESC LIMIT 60`)).all(agentId),
(await database.prepare(`SELECT id,slug,title,COALESCE(summary,'') AS summary,updated_at
FROM builds WHERE owner_id=? AND visibility='public'
AND guide_body IS NOT NULL AND length(trim(guide_body))>0
ORDER BY updated_at DESC LIMIT 60`)).all(agentId),
(await database.prepare(`SELECT comments.id,comments.created_at,
thread.target_type,thread.target_id,builds.title AS build_title,builds.slug,
COALESCE(target_profile.display_name,target_user.username) AS target_display_name,
target_profile.public_slug AS target_public_slug
FROM comments
JOIN comment_threads thread ON thread.id=comments.thread_id
LEFT JOIN builds ON builds.id=thread.target_id AND thread.target_type IN ('build','guide')
LEFT JOIN users target_user ON target_user.id=thread.target_id AND thread.target_type='profile'
LEFT JOIN user_profiles target_profile ON target_profile.user_id=target_user.id
LEFT JOIN deleted_accounts target_deleted ON target_deleted.user_id=target_user.id
LEFT JOIN user_status target_status ON target_status.user_id=target_user.id
WHERE comments.author_id=? AND comments.status='visible' AND (
(thread.target_type IN ('build','guide') AND builds.visibility='public')
OR (thread.target_type='profile' AND COALESCE(target_profile.is_public,0)=1
AND target_deleted.user_id IS NULL AND target_status.blocked_at IS NULL
AND (target_status.banned_until IS NULL OR target_status.banned_until<=?))
)
ORDER BY comments.created_at DESC LIMIT 50`)).all(agentId, new Date().toISOString()),
(await database.prepare(`SELECT comments.id,comments.author_id,comments.parent_id,comments.body_markdown,
comments.status,comments.created_at,
COALESCE(profile.display_name,users.username,'Deleted Agent') AS display_name,
profile.public_slug,
avatar.source AS avatar_source,avatar.value AS avatar_value,avatar.updated_at AS avatar_updated_at
FROM comment_threads thread
JOIN comments ON comments.thread_id=thread.id AND comments.status='visible'
LEFT JOIN users ON users.id=comments.author_id
LEFT JOIN user_profiles profile ON profile.user_id=users.id
LEFT JOIN user_avatars avatar ON avatar.user_id=users.id
WHERE thread.target_type='profile' AND thread.target_id=?
ORDER BY comments.created_at DESC LIMIT 100`)).all(agentId),
(await database.prepare(`SELECT COUNT(*) AS count FROM comments
JOIN comment_threads thread ON thread.id=comments.thread_id
LEFT JOIN builds ON builds.id=thread.target_id AND thread.target_type IN ('build','guide')
LEFT JOIN user_profiles target_profile ON target_profile.user_id=thread.target_id
AND thread.target_type='profile'
LEFT JOIN deleted_accounts target_deleted ON target_deleted.user_id=thread.target_id
LEFT JOIN user_status target_status ON target_status.user_id=thread.target_id
WHERE comments.author_id=? AND comments.status='visible' AND (
(thread.target_type IN ('build','guide') AND builds.visibility='public')
OR (thread.target_type='profile' AND COALESCE(target_profile.is_public,0)=1
AND target_deleted.user_id IS NULL AND target_status.blocked_at IS NULL
AND (target_status.banned_until IS NULL OR target_status.banned_until<=?))
)`)).get(agentId, new Date().toISOString()),
(await database.prepare(`SELECT COUNT(*) AS count FROM comments
JOIN comment_threads thread ON thread.id=comments.thread_id
WHERE thread.target_type='profile' AND thread.target_id=? AND comments.status='visible'`))
.get(agentId),
(await database.prepare(`SELECT
SUM(CASE WHEN guide_body IS NULL OR length(trim(guide_body))=0 THEN 1 ELSE 0 END) AS builds,
SUM(CASE WHEN guide_body IS NOT NULL AND length(trim(guide_body))>0 THEN 1 ELSE 0 END) AS guides
FROM builds WHERE owner_id=? AND visibility='public'`)).get(agentId)
]);
const loadedCommentIds = new Set(profileCommentRows.map((comment) => String(comment.id)));
let missingParentIds = [...new Set(profileCommentRows
.map((comment) => comment.parent_id ? String(comment.parent_id) : '')
.filter((parentId) => parentId && !loadedCommentIds.has(parentId)))];
for (let level = 0; missingParentIds.length && level < 100; level += 1) {
const placeholders = missingParentIds.map(() => '?').join(',');
const ancestors = await (await database.prepare(`SELECT comments.id,comments.author_id,
comments.parent_id,comments.body_markdown,comments.status,comments.created_at,
COALESCE(profile.display_name,users.username,'Deleted Agent') AS display_name,
profile.public_slug,
avatar.source AS avatar_source,avatar.value AS avatar_value,
avatar.updated_at AS avatar_updated_at
FROM comments
JOIN comment_threads thread ON thread.id=comments.thread_id
LEFT JOIN users ON users.id=comments.author_id
LEFT JOIN user_profiles profile ON profile.user_id=users.id
LEFT JOIN user_avatars avatar ON avatar.user_id=users.id
WHERE comments.id IN (${placeholders})
AND comments.status IN ('visible','removed')
AND thread.target_type='profile' AND thread.target_id=?`))
.all(...missingParentIds, agentId);
if (!ancestors.length) break;
profileCommentRows.push(...ancestors);
for (const ancestor of ancestors) loadedCommentIds.add(String(ancestor.id));
missingParentIds = [...new Set(ancestors
.map((ancestor) => ancestor.parent_id ? String(ancestor.parent_id) : '')
.filter((parentId) => parentId && !loadedCommentIds.has(parentId)))];
}
const commentAuthorIds = [...new Set(profileCommentRows
.map((comment) => comment.author_id ? String(comment.author_id) : '')
.filter(Boolean))];
const commentRoleRows = commentAuthorIds.length
? await (await database.prepare(`SELECT user_roles.user_id,roles.name
FROM user_roles
JOIN roles ON roles.id=user_roles.role_id
WHERE user_roles.user_id IN (${commentAuthorIds.map(() => '?').join(',')})
ORDER BY roles.name COLLATE NOCASE,roles.id`)).all(...commentAuthorIds)
: [];
const rolesByCommentAuthor = new Map<string, string[]>();
for (const row of commentRoleRows) {
const userId = String(row.user_id);
const roles = rolesByCommentAuthor.get(userId) ?? [];
roles.push(String(row.name));
rolesByCommentAuthor.set(userId, roles);
}
const threadedProfileComments = orderProfileCommentThread(profileCommentRows.map((comment) => {
const status = String(comment.status) === 'removed' ? 'removed' as const : 'visible' as const;
return {
id: String(comment.id),
parentId: comment.parent_id ? String(comment.parent_id) : null,
status,
bodyHtml: status === 'visible' ? renderUserMarkdown(comment.body_markdown) : '',
createdAt: String(comment.created_at),
author: {
id: String(comment.author_id),
displayName: String(comment.display_name),
publicSlug: comment.public_slug ? String(comment.public_slug) : null,
avatarUrl: avatarUrl(
comment.avatar_source,
comment.avatar_value,
String(comment.author_id),
comment.avatar_updated_at
),
roles: rolesByCommentAuthor.get(String(comment.author_id)) ?? []
},
canDelete: status === 'visible' && viewer?.id === String(comment.author_id),
canReport: status === 'visible' && Boolean(viewer) && viewer?.id !== String(comment.author_id)
};
}));
const followedRootIds = viewer && threadedProfileComments.length
? new Set((await (await database.prepare(`SELECT root_comment_id
FROM comment_thread_follows
WHERE user_id=? AND root_comment_id IN (${[...new Set(threadedProfileComments.map((comment) => comment.rootId))]
.map(() => '?').join(',')})`)).all(
viewer.id,
...[...new Set(threadedProfileComments.map((comment) => comment.rootId))]
)).map((row) => String(row.root_comment_id)))
: new Set<string>();
return {
agent: {
id: String(agent.id),
displayName: String(agent.display_name),
publicSlug: String(agent.public_slug),
avatarUrl: avatarUrl(
agent.avatar_source,
agent.avatar_value,
String(agent.id),
agent.avatar_updated_at
),
bioHtml: agent.bio_hidden ? '' : renderUserMarkdown(agent.bio_markdown),
bioMarkdown: isSelf
? String(pendingBioRevision?.body_markdown ?? agent.bio_markdown ?? '')
: '',
isPublic: Boolean(agent.is_public),
bioHidden: Boolean(agent.bio_hidden),
bioModerationReason: isSelf && agent.moderation_reason
? String(agent.moderation_reason)
: null,
bioReviewPending: Boolean(pendingBioRevision),
bioReviewReason: pendingBioRevision?.reason ? String(pendingBioRevision.reason) : null,
roles: roleRows.map((role) => String(role.name))
},
counts: {
builds: Number(contentCounts?.builds ?? 0),
guides: Number(contentCounts?.guides ?? 0),
authoredComments: Number(authoredCount?.count ?? 0),
profileComments: Number(profileCommentCount?.count ?? 0)
},
builds: buildRows.map((build) => ({
id: String(build.id),
slug: String(build.slug),
title: String(build.title),
summary: String(build.summary ?? ''),
updatedAt: String(build.updated_at)
})),
guides: guideRows.map((guide) => ({
id: String(guide.id),
slug: String(guide.slug),
title: String(guide.title),
summary: String(guide.summary ?? ''),
updatedAt: String(guide.updated_at)
})),
authoredComments: authoredCommentRows.map((comment) => {
const targetType = String(comment.target_type);
const targetId = String(comment.target_id);
return {
id: String(comment.id),
createdAt: String(comment.created_at),
targetType,
targetTitle: targetType === 'profile'
? `${String(comment.target_display_name ?? 'Agent')} profile`
: String(comment.build_title ?? 'Community content'),
targetHref: commentHref(
targetType,
comment.slug,
targetId,
comment.target_public_slug,
String(comment.id)
)
};
}),
profileComments: threadedProfileComments.map((comment) => ({
...comment,
isFollowing: followedRootIds.has(comment.rootId)
})),
viewer: {
id: viewer?.id ?? null,
canComment: Boolean(viewer) && Boolean(agent.is_public),
canReportProfile: Boolean(viewer) && !isSelf,
isSelf
},
accountControl
};
};
export const actions: Actions = {
saveBio: async ({ params, cookies, request }) => {
const viewer = await getSessionUser(cookies);
if (!viewer) return fail(401, { bioError: 'Sign in to edit your profile.', bioResultToken: randomUUID() });
const database = await getDatabase();
const owner = await resolveAgentProfile(database, params.id);
if (!owner || owner.id !== viewer.id) {
return fail(403, { bioError: 'You can edit only your own profile.', bioResultToken: randomUUID() });
}
const result = await saveProfileBio(viewer, (await request.formData()).get('bio'));
if (!result.ok) {
return fail(400, { bioError: result.error, bioResultToken: randomUUID() });
}
return {
bioSaved: true,
bioReviewQueued: result.reviewQueued,
bioMarkdown: result.bio,
bioResultToken: randomUUID()
};
},
setProfileVisibility: async ({ params, cookies, request }) => {
const viewer = await getSessionUser(cookies);
if (!viewer) return fail(401, {
profileVisibilityError: 'Sign in to change profile visibility.',
profileVisibilityResultToken: randomUUID()
});
const database = await getDatabase();
const owner = await resolveAgentProfile(database, params.id);
if (!owner || owner.id !== viewer.id) {
return fail(403, {
profileVisibilityError: 'You can change only your own profile.',
profileVisibilityResultToken: randomUUID()
});
}
const result = await updateProfileVisibility(
viewer,
(await request.formData()).get('isPublic')
);
if (!result.ok) {
return fail(400, {
profileVisibilityError: result.error,
profileVisibilityResultToken: randomUUID()
});
}
return {
profileVisibilitySaved: true,
profileIsPublic: result.isPublic,
profileVisibilityResultToken: randomUUID()
};
},
commentProfile: async ({ params, cookies, request }) => {
const commenter = await getSessionUser(cookies);
const formData = await request.formData();
const parentId = String(formData.get('parentId') ?? '').trim() || null;
const commentFailure = (status: number, profileCommentError: string) => fail(status, {
profileCommentError,
profileCommentParentId: parentId,
profileCommentResultToken: randomUUID()
});
if (!commenter) return commentFailure(401, 'Sign in to post a profile comment.');
const body = normalizeUserMarkdown(
formData.get('body'),
COMMENT_MAX_CHARACTERS
);
if (body === null || Array.from(body).length < 2) {
return commentFailure(
400,
`Comments must contain 2–${COMMENT_MAX_CHARACTERS.toLocaleString()} visible characters.`
);
}
const database = await getDatabase();
const owner = await resolveAgentProfile(database, params.id);
if (!owner) return commentFailure(404, 'Agent profile not found.');
const agentId = owner.id;
const moderationPolicy = await getCommunityModerationPolicy();
const now = new Date();
const rows = await (await database.prepare(`SELECT users.id,
COALESCE(profile.display_name,users.username) AS display_name,profile.is_public,
status.blocked_at,status.banned_until,deleted.deleted_at
FROM users
LEFT JOIN user_profiles profile ON profile.user_id=users.id
LEFT JOIN user_status status ON status.user_id=users.id
LEFT JOIN deleted_accounts deleted ON deleted.user_id=users.id
WHERE users.id IN (?,?)`)).all(commenter.id, agentId);
const commenterState = rows.find((row) => row.id === commenter.id);
const ownerState = rows.find((row) => row.id === agentId);
if (unavailable(commenterState)) {
return commentFailure(403, 'This account cannot post comments.');
}
if (unavailable(ownerState) || !Boolean(ownerState?.is_public)) {
return commentFailure(404, 'Agent profile not found.');
}
const recent = await (await database.prepare(`SELECT comments.body_markdown,comments.created_at
FROM comments JOIN comment_threads thread ON thread.id=comments.thread_id
WHERE comments.author_id=? AND comments.status IN ('visible','pending_review')
AND thread.target_type='profile' AND thread.target_id=?
ORDER BY comments.created_at DESC LIMIT 1`)).get(commenter.id, agentId);
if (typeof recent?.created_at === 'string') {
const elapsed = (now.getTime() - new Date(recent.created_at).getTime()) / 1_000;
if (elapsed < RAPID_COMMENT_SECONDS) {
return commentFailure(429, 'Wait a few seconds before posting again.');
}
if (elapsed < moderationPolicy.duplicateWindowSeconds && recent.body_markdown === body) {
return commentFailure(409, 'That comment was already posted.');
}
}
const moderation = await moderateCommunityText({
contentType: 'comment',
text: body,
policy: moderationPolicy
});
const commentStatus = moderation.decision === 'approved' ? 'visible' : 'pending_review';
const createdAt = now.toISOString();
const proposedThreadId = randomUUID();
await (await database.prepare(`INSERT OR IGNORE INTO comment_threads
(id,target_type,target_id,created_at) VALUES(?,'profile',?,?)`))
.run(proposedThreadId, agentId, createdAt);
const thread = await (await database.prepare(
"SELECT id FROM comment_threads WHERE target_type='profile' AND target_id=?"
)).get(agentId);
if (!thread?.id) return commentFailure(500, 'The profile thread could not be opened.');
const parent = parentId
? await (await database.prepare(`SELECT parent.author_id
FROM comments parent
JOIN comment_threads thread ON thread.id=parent.thread_id
WHERE parent.id=? AND parent.status='visible' AND thread.id=?
AND thread.target_type='profile' AND thread.target_id=?`))
.get(parentId, thread.id, agentId)
: null;
if (parentId && !parent) {
return commentFailure(404, 'That message is no longer available for replies.');
}
const commentId = randomUUID();
const inserted = parentId
? await (await database.prepare(`INSERT INTO comments
(id,thread_id,author_id,parent_id,body_markdown,status,created_at,updated_at)
SELECT ?,parent.thread_id,?,parent.id,?,?,?,?
FROM comments parent
JOIN comment_threads thread ON thread.id=parent.thread_id
WHERE parent.id=? AND parent.status='visible' AND thread.id=?
AND thread.target_type='profile' AND thread.target_id=?`))
.run(
commentId,
commenter.id,
body,
commentStatus,
createdAt,
createdAt,
parentId,
thread.id,
agentId
)
: await (await database.prepare(`INSERT INTO comments
(id,thread_id,author_id,parent_id,body_markdown,status,created_at,updated_at)
VALUES(?,?,?,NULL,?,?,?,?)`))
.run(commentId, thread.id, commenter.id, body, commentStatus, createdAt, createdAt);
if (!inserted.changes) {
return commentFailure(409, 'That conversation changed before the reply could be posted.');
}
await recordCommunityModerationScan({
contentType: 'comment',
contentId: commentId,
authorId: commenter.id,
result: moderation
});
const parentAuthorId = parent?.author_id ? String(parent.author_id) : null;
const recipientId = profileCommentNotificationRecipient(
commenter.id,
parentAuthorId,
agentId
);
if (commentStatus === 'visible' && recipientId) {
const profileLink = agentProfileHref(owner, `profile-comment-${commentId}`);
const isDirectReplyRecipient = Boolean(parentAuthorId && recipientId === parentAuthorId);
await createNotification({
userId: recipientId,
category: 'comments',
source: 'agent',
title: parentId ? 'New profile reply' : 'New profile comment',
body: parentId
? isDirectReplyRecipient
? `${commenter.displayName} replied to your message on ${owner.displayName}'s Agent profile.`
: `${commenter.displayName} replied in a conversation on your Agent profile.`
: `${commenter.displayName} posted a comment on your Agent profile.`,
link: profileLink,
actions: [{
label: parentId ? 'VIEW REPLY' : 'VIEW COMMENT',
href: profileLink
}]
});
}
if (commentStatus === 'visible' && parentId) {
await notifyProfileCommentFollowers({
commentId,
authorId: commenter.id,
authorDisplayName: commenter.displayName,
profileOwnerId: agentId,
profileOwnerDisplayName: owner.displayName,
profileOwnerPublicSlug: owner.publicSlug,
directRecipientId: recipientId
});
}
return {
profileCommentSaved: true,
profileCommentReviewQueued: commentStatus === 'pending_review',
profileCommentId: commentId,
profileCommentParentId: parentId,
profileCommentResultToken: randomUUID()
};
},
toggleProfileCommentFollow: async ({ params, cookies, request }) => {
const viewer = await getSessionUser(cookies);
const formData = await request.formData();
const commentId = String(formData.get('commentId') ?? '').trim();
const intent = String(formData.get('intent') ?? '').trim();
const failure = (status: number, message: string) => fail(status, {
profileCommentFollowError: message,
profileCommentFollowId: commentId,
profileCommentFollowResultToken: randomUUID()
});
if (!viewer) return failure(401, 'Sign in to follow a conversation.');
if (!commentId || !['follow', 'unfollow'].includes(intent)) {
return failure(400, 'Choose a conversation and follow preference.');
}
const database = await getDatabase();
const owner = await resolveAgentProfile(database, params.id);
if (!owner) return failure(404, 'Agent profile not found.');
const conversation = await profileCommentConversation(commentId, owner.id);
if (!conversation) return failure(404, 'That conversation is no longer available.');
if (intent === 'follow') {
await (await database.prepare(`INSERT OR IGNORE INTO comment_thread_follows
(user_id,root_comment_id,created_at) VALUES(?,?,?)`))
.run(viewer.id, conversation.rootId, new Date().toISOString());
} else {
await (await database.prepare(
'DELETE FROM comment_thread_follows WHERE user_id=? AND root_comment_id=?'
)).run(viewer.id, conversation.rootId);
}
return {
profileCommentFollowSaved: true,
profileCommentFollowing: intent === 'follow',
profileCommentFollowId: commentId,
profileCommentFollowResultToken: randomUUID()
};
},
deleteProfileComment: async ({ params, cookies, request }) => {
const viewer = await getSessionUser(cookies);
if (!viewer) return fail(401, { profileCommentError: 'Sign in to remove a comment.' });
const commentId = String((await request.formData()).get('commentId') ?? '').trim();
if (!commentId) return fail(400, { profileCommentError: 'Choose a comment to remove.' });
const database = await getDatabase();
const owner = await resolveAgentProfile(database, params.id);
if (!owner) return fail(404, { profileCommentError: 'Agent profile not found.' });
const comment = await (await database.prepare(`SELECT comments.id,comments.author_id
FROM comments JOIN comment_threads thread ON thread.id=comments.thread_id
WHERE comments.id=? AND comments.status='visible'
AND thread.target_type='profile' AND thread.target_id=?`)).get(commentId, owner.id);
if (!comment) return fail(404, { profileCommentError: 'That comment is no longer available.' });
if (comment.author_id !== viewer.id) {
return fail(403, { profileCommentError: 'You can remove only your own comment.' });
}
const removedAt = new Date().toISOString();
const removed = await (await database.prepare(`UPDATE comments SET status='removed',removed_at=?,removed_by=?,
removal_reason='Removed by author',updated_at=? WHERE id=? AND status='visible'`))
.run(removedAt, viewer.id, removedAt, commentId);
if (!removed.changes) {
return fail(409, { profileCommentError: 'That comment changed before it could be removed.' });
}
return {
profileCommentDeleted: true,
profileCommentId: commentId,
profileCommentResultToken: randomUUID()
};
},
reportContent: async ({ params, cookies, request }) => {
const reporter = await getSessionUser(cookies);
const form = await request.formData();
const contentType = String(form.get('contentType') ?? '');
const contentId = String(form.get('contentId') ?? '').trim();
const category = String(form.get('category') ?? '');
const details = String(form.get('details') ?? '').normalize('NFKC').trim();
const failure = (status: number, message: string) => fail(status, {
userReportError: message,
userReportContentId: contentId,
userReportContentType: contentType,
userReportResultToken: randomUUID()
});
if (!reporter) return failure(401, 'Sign in to request an Oversight review.');
if (!['comment', 'profile_bio', 'avatar'].includes(contentType)) {
return failure(400, 'Choose reportable content.');
}
if (!['spam', 'harassment', 'hate', 'sexual', 'violence', 'self_harm', 'privacy', 'other'].includes(category)) {
return failure(400, 'Choose the policy concern that best fits this report.');
}
if (details.length > 500 || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u.test(details)) {
return failure(400, 'Report details may contain up to 500 visible characters.');
}
const database = await getDatabase();
const owner = await resolveAgentProfile(database, params.id);
if (!owner) return failure(404, 'Agent profile not found.');
const agentId = owner.id;
if (contentType === 'comment') {
const comment = await (await database.prepare(`SELECT comments.author_id
FROM comments JOIN comment_threads thread ON thread.id=comments.thread_id
WHERE comments.id=? AND comments.status='visible'
AND thread.target_type='profile' AND thread.target_id=?`)).get(contentId, agentId);
if (!comment) return failure(404, 'That comment is no longer available.');
if (String(comment.author_id) === reporter.id) return failure(400, 'You cannot report your own comment.');
} else {
if (contentId !== agentId) return failure(400, 'That profile content does not match this Agent.');
if (reporter.id === agentId) return failure(400, 'You cannot report your own profile content.');
const profile = await (await database.prepare(`SELECT profile.bio_markdown,profile.is_public,
COALESCE(moderation.bio_hidden,0) AS bio_hidden,avatar.source AS avatar_source
FROM user_profiles profile
LEFT JOIN profile_moderation moderation ON moderation.user_id=profile.user_id
LEFT JOIN user_avatars avatar ON avatar.user_id=profile.user_id
WHERE profile.user_id=?`)).get(agentId);
if (!profile?.is_public) return failure(404, 'That profile is no longer available.');
if (contentType === 'profile_bio' && (profile.bio_hidden || !String(profile.bio_markdown ?? '').trim())) {
return failure(404, 'That biography is no longer available.');
}
if (contentType === 'avatar' && profile.avatar_source !== 'upload') {
return failure(400, 'Preset SHD avatars are managed site assets and do not need an Agent report.');
}
}
const report = await createUserContentReport({
contentType: contentType as 'comment' | 'profile_bio' | 'avatar',
contentId,
reporterId: reporter.id,
category: category as 'spam' | 'harassment' | 'hate' | 'sexual' | 'violence' | 'self_harm' | 'privacy' | 'other',
details
});
if (!report) return failure(409, 'You already have a pending report for this content.');
if (contentType === 'avatar') {
await queueCurrentAvatarReview(database, agentId, { origin: 'user_report' });
}
return {
userReportSaved: true,
userReportContentId: contentId,
userReportContentType: contentType,
userReportResultToken: randomUUID()
};
}
};