admin can delete status changes from post timeline
This commit is contained in:
@@ -509,6 +509,26 @@ export default async function adminPostRoutes(app: FastifyInstance) {
|
||||
}
|
||||
);
|
||||
|
||||
// delete a status change entry
|
||||
app.delete<{ Params: { id: string } }>(
|
||||
"/admin/status-changes/:id",
|
||||
{ preHandler: [app.requireAdmin], config: { rateLimit: { max: 20, timeWindow: "1 minute" } } },
|
||||
async (req, reply) => {
|
||||
const sc = await prisma.statusChange.findUnique({ where: { id: req.params.id }, select: { id: true, postId: true, toStatus: true } });
|
||||
if (!sc) {
|
||||
reply.status(404).send({ error: "Status change not found" });
|
||||
return;
|
||||
}
|
||||
// delete related notifications
|
||||
await prisma.notification.deleteMany({
|
||||
where: { postId: sc.postId, type: "status_changed" },
|
||||
});
|
||||
await prisma.statusChange.delete({ where: { id: sc.id } });
|
||||
req.log.info({ adminId: req.adminId, statusChangeId: sc.id }, "status change deleted");
|
||||
reply.status(204).send();
|
||||
}
|
||||
);
|
||||
|
||||
app.post<{ Params: { id: string }; Body: z.infer<typeof rollbackBody> }>(
|
||||
"/admin/posts/:id/rollback",
|
||||
{ preHandler: [app.requireAdmin], config: { rateLimit: { max: 10, timeWindow: "1 minute" } } },
|
||||
|
||||
@@ -51,6 +51,7 @@ export default function Timeline({
|
||||
onReply,
|
||||
onShowEditHistory,
|
||||
onLockComment,
|
||||
onDeleteStatusChange,
|
||||
}: {
|
||||
entries: TimelineEntry[]
|
||||
onReact?: (entryId: string, emoji: string) => void
|
||||
@@ -61,6 +62,7 @@ export default function Timeline({
|
||||
onReply?: (entry: TimelineEntry) => void
|
||||
onShowEditHistory?: (entryId: string) => void
|
||||
onLockComment?: (entryId: string) => void
|
||||
onDeleteStatusChange?: (entryId: string) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
@@ -76,6 +78,7 @@ export default function Timeline({
|
||||
onReply={onReply}
|
||||
onShowEditHistory={onShowEditHistory}
|
||||
onLockComment={onLockComment}
|
||||
onDeleteStatusChange={onDeleteStatusChange}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
@@ -93,6 +96,7 @@ function TimelineItem({
|
||||
onReply,
|
||||
onShowEditHistory,
|
||||
onLockComment,
|
||||
onDeleteStatusChange,
|
||||
index,
|
||||
}: {
|
||||
entry: TimelineEntry
|
||||
@@ -104,6 +108,7 @@ function TimelineItem({
|
||||
onReply?: (entry: TimelineEntry) => void
|
||||
onShowEditHistory?: (entryId: string) => void
|
||||
onLockComment?: (entryId: string) => void
|
||||
onDeleteStatusChange?: (entryId: string) => void
|
||||
index: number
|
||||
}) {
|
||||
const confirm = useConfirm()
|
||||
@@ -138,6 +143,19 @@ function TimelineItem({
|
||||
<time dateTime={entry.createdAt} style={{ color: 'var(--text-tertiary)', fontSize: 'var(--text-xs)' }}>
|
||||
{new Date(entry.createdAt).toLocaleDateString()}
|
||||
</time>
|
||||
{isCurrentAdmin && onDeleteStatusChange && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!await confirm('Remove this status change?')) return
|
||||
onDeleteStatusChange(entry.id)
|
||||
}}
|
||||
className="action-btn"
|
||||
aria-label="Remove status change"
|
||||
style={{ color: 'var(--text-tertiary)', padding: '2px 4px', borderRadius: 'var(--radius-sm)', minHeight: 24 }}
|
||||
>
|
||||
<IconTrash size={12} stroke={2} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{entry.reason && (
|
||||
<div
|
||||
|
||||
@@ -931,6 +931,15 @@ export default function PostDetail() {
|
||||
onReply={post.isThreadLocked ? undefined : handleReply}
|
||||
onShowEditHistory={showCommentHistory}
|
||||
onLockComment={admin.isAdmin ? toggleCommentEditLock : undefined}
|
||||
onDeleteStatusChange={admin.isAdmin ? async (id) => {
|
||||
try {
|
||||
await api.delete(`/admin/status-changes/${id}`)
|
||||
toast.success('Status change removed')
|
||||
fetchPost()
|
||||
} catch {
|
||||
toast.error('Failed to remove status change')
|
||||
}
|
||||
} : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user