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:
parent
f1239ad593
commit
dbf7a0f180
2 changed files with 98 additions and 1 deletions
37
src/collections/tickets/TicketVotes.ts
Normal file
37
src/collections/tickets/TicketVotes.ts
Normal 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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue