1
0
Fork 0

feat(tickets): add ticket collections and edit history

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-01 17:48:34 -04:00
parent f1239ad593
commit dbf7a0f180
2 changed files with 98 additions and 1 deletions

View file

@ -0,0 +1,37 @@
import type { CollectionConfig } from "payload";
export const TicketVotes: CollectionConfig = {
slug: "ticket-votes",
admin: {
group: "Helpdesk",
defaultColumns: ["ticket", "user", "createdAt"],
},
access: {
read: ({ req }) => Boolean(req.user),
create: () => false,
update: () => false,
delete: () => false,
},
indexes: [
{
fields: ["ticket", "user"],
unique: true,
},
],
fields: [
{
name: "ticket",
type: "relationship",
relationTo: "tickets",
required: true,
index: true,
},
{
name: "user",
type: "relationship",
relationTo: "users",
required: true,
index: true,
},
],
};

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from "payload";
import { requirePermission, hasPermission } from "@/utils/access-control/hasPermission";
import { hasPermission } from "@/utils/access-control/hasPermission";
import {
CLOSING_STATUSES,
STATUS_LABEL,
@ -30,6 +30,20 @@ const systemEntry = (message: string, createdAt?: string) => ({
created_at: createdAt ?? new Date().toISOString(),
});
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function immutableEditValue(value: unknown): string | null {
if (!isRecord(value)) return null;
return JSON.stringify({
before: value.before,
after: value.after,
editedBy: resolveUserId(value.editedBy),
editedAt: value.editedAt,
});
}
export const Tickets: CollectionConfig = {
slug: "tickets",
admin: {
@ -74,6 +88,17 @@ export const Tickets: CollectionConfig = {
// entry appended by the create server action instead.
if (!prev || !prev.id) return data;
if (next.editHistory !== undefined) {
const previousEdits = Array.isArray(prev.editHistory) ? prev.editHistory : [];
const nextEdits = Array.isArray(next.editHistory) ? next.editHistory : [];
const existingEditsChanged = previousEdits.some(
(entry, index) => immutableEditValue(entry) !== immutableEditValue(nextEdits[index]),
);
if (nextEdits.length < previousEdits.length || existingEditsChanged) {
throw new Error("Ticket edit history is immutable.");
}
}
const entries = [...((next.activity as unknown[]) ?? [])];
const now = new Date().toISOString();
const changes: string[] = [];
@ -230,6 +255,41 @@ export const Tickets: CollectionConfig = {
},
],
},
{
name: "editHistory",
type: "array",
admin: {
readOnly: true,
description: "Immutable before/after snapshots captured when the author edits a ticket.",
},
fields: [
{
name: "before",
type: "json",
required: true,
},
{
name: "after",
type: "json",
required: true,
},
{
name: "editedBy",
type: "relationship",
relationTo: "users",
required: true,
},
{
name: "editedAt",
type: "date",
required: true,
admin: {
readOnly: true,
date: { pickerAppearance: "dayAndTime" },
},
},
],
},
{
name: "closedAt",
type: "date",