diff --git a/app/components/comment/CommentItem.vue b/app/components/comment/CommentItem.vue index 3e4a95d..92f4e15 100644 --- a/app/components/comment/CommentItem.vue +++ b/app/components/comment/CommentItem.vue @@ -135,7 +135,7 @@ function initials(name: string | null) { Edit - + Delete diff --git a/app/components/post/PostDetail.vue b/app/components/post/PostDetail.vue index d9f4fdb..03c7da7 100644 --- a/app/components/post/PostDetail.vue +++ b/app/components/post/PostDetail.vue @@ -381,7 +381,7 @@ async function handleShare() { Edit - Delete + Delete diff --git a/app/components/post/PostDetailModal.vue b/app/components/post/PostDetailModal.vue index ef92300..9473303 100644 --- a/app/components/post/PostDetailModal.vue +++ b/app/components/post/PostDetailModal.vue @@ -30,6 +30,7 @@ watch(open, (isOpen) => { | ComputedRef, type: ResourceType) { const { data: session } = useAuthSession() const ctx = useOrgContext() @@ -38,7 +37,13 @@ export function usePermission(authorId: Ref | ComputedRef isOwner.value || canModerate.value) + const canDelete = computed(() => { + // Posts: moderator-only. Once shipped, a post belongs to the community + // (others' upvotes / comments / status). Authors can edit but not delete. + if (type === 'post') return canModerate.value + // Comments are conversational — author can delete their own. + return isOwner.value || canModerate.value + }) const showMenu = computed(() => canEdit.value || canDelete.value) diff --git a/server/api/admin/posts/[id].delete.ts b/server/api/admin/posts/[id].delete.ts index 84fa73a..2380d93 100644 --- a/server/api/admin/posts/[id].delete.ts +++ b/server/api/admin/posts/[id].delete.ts @@ -1,16 +1,16 @@ import { and, eq, sql } from 'drizzle-orm' import { post, comment, commentLike, vote } from '#layers/feedlog/server/db/schemas' -// DELETE /api/admin/posts/:id — Hard delete. Author can delete own; -// moderators can delete anyone's (escalates via feedlog:moderate). The -// "admin" path is here for the cascade clean-up logic, not for the gate. +// DELETE /api/admin/posts/:id — Moderator-only hard delete. Once a post is +// public it belongs to the community (others' upvotes/comments/status); the +// author has no self-delete path, only edit via PATCH /api/posts/:id. export default defineEventHandler(async (event) => { - const { session, orgId } = await requireOrgMember(event) + const { orgId } = await requireOrgPermission(event, { feedlog: ['moderate'] }) const id = getRouterParam(event, 'id')! const db = useDB() const [existing] = await db - .select({ authorId: post.authorId, mergedTo: post.mergedTo }) + .select({ mergedTo: post.mergedTo }) .from(post) .where(and(eq(post.id, id), eq(post.orgId, orgId))) .limit(1) @@ -21,10 +21,6 @@ export default defineEventHandler(async (event) => { throw createError({ statusCode: 400, message: 'Cannot delete a merged post. Unmerge it first.' }) } - if (existing.authorId !== session.user.id) { - await requireOrgPermission(event, { feedlog: ['moderate'] }) - } - // Delete associated data (no DB foreign keys, cascade at application level) // 1. Delete all comment likes for this post await db.delete(commentLike).where(