1
0
Fork 0
polaris-task-force/src/collections/voiceover/VoiceSubmissions.ts
Z8MB1E a0fa8889fc feat(voiceover): add take description field to voice submissions
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-07 04:36:52 -04:00

89 lines
2.9 KiB
TypeScript

import { CollectionConfig } from "payload";
import type { User } from "@/payload-types";
import hasRoles from "@/utils/access-control/hasRoles";
import { isSuperuser } from "@/utils/access-control/hasPermission";
export const VoiceSubmissions: CollectionConfig = {
slug: "voice-submissions",
admin: {
group: "Voiceover",
useAsTitle: "id",
defaultColumns: ["script", "user", "status", "createdAt"],
},
access: {
read: async ({ req }) => {
const user = req.user as User | null;
if (!user) return false;
// Elevation bypasses the owner scope so reviewers see every submission;
// regular users only ever see their own.
if (await isSuperuser(req.payload, user)) return true;
if (hasRoles(["admin", "developer"], user)) return true;
return { user: { equals: user.id } };
},
create: ({ req, data }) => {
const user = req.user as User | null;
if (!user) return false;
if (hasRoles(["admin", "developer"], user)) return true;
// Submitters may only create their own submissions. Server actions that
// write on behalf of users run with overrideAccess and set `user` themselves.
const submittedUser =
typeof data?.user === "object" && data?.user !== null ? data.user.id : data?.user;
return submittedUser === user.id;
},
// Status changes are review actions — submitters must not edit their own docs.
update: ({ req }) => hasRoles(["admin", "developer"], req.user),
delete: ({ req }) => hasRoles(["developer"], req.user),
},
fields: [
{
name: "script",
type: "relationship",
relationTo: "voice-scripts",
required: true,
index: true,
},
{
name: "user",
type: "relationship",
relationTo: "users",
required: true,
index: true,
admin: {
description: "Submitter",
},
},
{
name: "description",
type: "textarea",
maxLength: 300,
admin: {
description: "Optional short note from the submitter about this take.",
},
},
{
name: "status",
type: "select",
required: true,
defaultValue: "pending",
options: [
{ label: "Pending", value: "pending" },
{ label: "Accepted", value: "accepted" },
{ label: "Rejected", value: "rejected" },
],
},
{
name: "reviewNote",
type: "textarea",
admin: {
description: "Optional reviewer feedback. Shown to the submitter alongside their take.",
},
},
],
// No `upload.mimeTypes` allowlist on purpose: Payload content-sniffs uploads
// (file-type) and reports WebM recordings as `video/webm`, so an audio/* MIME
// allowlist rejects browser-recorded takes. The submit action is the gate
// instead (audio/* base type + size cap); restricted file types (executables,
// scripts, …) stay blocked by Payload's built-in checks.
upload: true,
timestamps: true,
};