1
0
Fork 0

feat(evaluations): add evaluations collection and migration

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-08-27 18:36:04 -04:00
parent 9955c376f9
commit 15145f613a
3 changed files with 19481 additions and 0 deletions

View file

@ -0,0 +1,88 @@
import { CollectionConfig } from "payload";
import { requirePermission } from "@/utils/access-control/hasPermission";
import { PERFORMANCE_LEVELS } from "@/lib/evaluations/levels";
/**
* Leadership evaluations.
*
* One row per (rater, ratee, mission, kind) — the uniqueness is enforced by the
* service layer (`upsertEvaluation` in `src/lib/evaluations`), not by a DB
* constraint.
*
* Privacy model: raw documents are only readable/writable by users holding the
* `evaluations:*` permissions (admin/developer via role docs). Normal users never
* receive raw rows over REST — the profile UI consumes pre-aggregated, rater-free
* output produced by server actions that call the service with explicit RBAC gating.
* `rater` and `comment` are private fields and must never be surfaced to normal
* users by any read path.
*/
export const Evaluations: CollectionConfig = {
slug: "evaluations",
admin: {
group: "Users",
defaultColumns: ["ratee", "rater", "mission", "kind", "level"],
},
access: {
read: requirePermission("evaluations:read"),
create: requirePermission("evaluations:create"),
update: requirePermission("evaluations:update"),
delete: requirePermission("evaluations:delete"),
},
fields: [
{
name: "rater",
type: "relationship",
relationTo: "users",
required: true,
admin: {
description:
"Who submitted this evaluation. Private — only visible to users with evaluations:read.",
},
},
{
name: "ratee",
type: "relationship",
relationTo: "users",
required: true,
},
{
name: "mission",
type: "relationship",
relationTo: "missions",
required: true,
},
{
name: "kind",
type: "select",
options: [
{
label: "Leader (direct)",
value: "leader-direct",
},
{
label: "Leader (indirect)",
value: "leader-indirect",
},
{
label: "Subordinate",
value: "subordinate",
},
],
required: true,
},
{
name: "level",
type: "select",
options: PERFORMANCE_LEVELS.map((l) => ({ label: l.label, value: l.id })),
required: true,
},
{
name: "comment",
type: "textarea",
admin: {
description:
"Private free-text note. Never shown to normal users; managers only.",
},
},
],
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,65 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_evaluations_kind" AS ENUM('leader-direct', 'leader-indirect', 'subordinate');
CREATE TYPE "public"."enum_evaluations_level" AS ENUM('exceptional', 'excellent', 'proficient', 'adequate', 'moderate', 'under-review');
ALTER TYPE "public"."enum_game_event_logs_target_collection" ADD VALUE 'evaluations' BEFORE 'tickets';
ALTER TYPE "public"."enum_users_preferences_notifications_muted_types" ADD VALUE 'award:granted' BEFORE 'account:discord-request';
ALTER TYPE "public"."enum_users_preferences_discord_muted_types" ADD VALUE 'award:granted' BEFORE 'account:discord-request';
ALTER TYPE "public"."enum_roles_permissions" ADD VALUE 'evaluations:create' BEFORE 'missions:create';
ALTER TYPE "public"."enum_roles_permissions" ADD VALUE 'evaluations:read' BEFORE 'missions:create';
ALTER TYPE "public"."enum_roles_permissions" ADD VALUE 'evaluations:update' BEFORE 'missions:create';
ALTER TYPE "public"."enum_roles_permissions" ADD VALUE 'evaluations:delete' BEFORE 'missions:create';
CREATE TABLE "evaluations" (
"id" serial PRIMARY KEY NOT NULL,
"rater_id" integer NOT NULL,
"ratee_id" integer NOT NULL,
"mission_id" integer NOT NULL,
"kind" "enum_evaluations_kind" NOT NULL,
"level" "enum_evaluations_level" NOT NULL,
"comment" varchar,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "evaluations_id" integer;
ALTER TABLE "evaluations" ADD CONSTRAINT "evaluations_rater_id_users_id_fk" FOREIGN KEY ("rater_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "evaluations" ADD CONSTRAINT "evaluations_ratee_id_users_id_fk" FOREIGN KEY ("ratee_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "evaluations" ADD CONSTRAINT "evaluations_mission_id_missions_id_fk" FOREIGN KEY ("mission_id") REFERENCES "public"."missions"("id") ON DELETE set null ON UPDATE no action;
CREATE INDEX "evaluations_rater_idx" ON "evaluations" USING btree ("rater_id");
CREATE INDEX "evaluations_ratee_idx" ON "evaluations" USING btree ("ratee_id");
CREATE INDEX "evaluations_mission_idx" ON "evaluations" USING btree ("mission_id");
CREATE INDEX "evaluations_updated_at_idx" ON "evaluations" USING btree ("updated_at");
CREATE INDEX "evaluations_created_at_idx" ON "evaluations" USING btree ("created_at");
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_evaluations_fk" FOREIGN KEY ("evaluations_id") REFERENCES "public"."evaluations"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "payload_locked_documents_rels_evaluations_id_idx" ON "payload_locked_documents_rels" USING btree ("evaluations_id");`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "evaluations" DISABLE ROW LEVEL SECURITY;
DROP TABLE "evaluations" CASCADE;
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_evaluations_fk";
ALTER TABLE "game_event_logs" ALTER COLUMN "target_collection" SET DATA TYPE text;
DROP TYPE "public"."enum_game_event_logs_target_collection";
CREATE TYPE "public"."enum_game_event_logs_target_collection" AS ENUM('game-structures', 'game-vehicles', 'game-npcs', 'structures', 'resources', 'assets', 'vehicles', 'factions', 'missions', 'campaigns', 'users', 'technologies', 'maps', 'shipments', 'bank-accounts', 'bank-transactions', 'ledger-entries', 'locker-storages', 'loadouts', 'market-listings', 'market-negotiations', 'tickets', 'game-servers');
ALTER TABLE "game_event_logs" ALTER COLUMN "target_collection" SET DATA TYPE "public"."enum_game_event_logs_target_collection" USING "target_collection"::"public"."enum_game_event_logs_target_collection";
ALTER TABLE "users_preferences_notifications_muted_types" ALTER COLUMN "value" SET DATA TYPE text;
DROP TYPE "public"."enum_users_preferences_notifications_muted_types";
CREATE TYPE "public"."enum_users_preferences_notifications_muted_types" AS ENUM('market:offer', 'market:counter', 'market:accept', 'market:reject', 'market:withdrawn', 'market:closed', 'market:sold', 'market:expired', 'account:discord-request', 'finance:deposit', 'finance:withdraw', 'finance:transfer', 'shipment:completed', 'ticket:created', 'ticket:assigned', 'ticket:reply', 'ticket:status');
ALTER TABLE "users_preferences_notifications_muted_types" ALTER COLUMN "value" SET DATA TYPE "public"."enum_users_preferences_notifications_muted_types" USING "value"::"public"."enum_users_preferences_notifications_muted_types";
ALTER TABLE "users_preferences_discord_muted_types" ALTER COLUMN "value" SET DATA TYPE text;
DROP TYPE "public"."enum_users_preferences_discord_muted_types";
CREATE TYPE "public"."enum_users_preferences_discord_muted_types" AS ENUM('market:offer', 'market:counter', 'market:accept', 'market:reject', 'market:withdrawn', 'market:closed', 'market:sold', 'market:expired', 'account:discord-request', 'finance:deposit', 'finance:withdraw', 'finance:transfer', 'shipment:completed', 'ticket:created', 'ticket:assigned', 'ticket:reply', 'ticket:status');
ALTER TABLE "users_preferences_discord_muted_types" ALTER COLUMN "value" SET DATA TYPE "public"."enum_users_preferences_discord_muted_types" USING "value"::"public"."enum_users_preferences_discord_muted_types";
ALTER TABLE "roles_permissions" ALTER COLUMN "value" SET DATA TYPE text;
DROP TYPE "public"."enum_roles_permissions";
CREATE TYPE "public"."enum_roles_permissions" AS ENUM('system:admin-access', 'users:create', 'users:read', 'users:update', 'users:delete', 'users:unlock', 'users:assign-roles', 'ranks:create', 'ranks:read', 'ranks:update', 'ranks:delete', 'profiles:create', 'profiles:read', 'profiles:update', 'profiles:delete', 'awards:create', 'awards:read', 'awards:update', 'awards:delete', 'qualifications:create', 'qualifications:read', 'qualifications:update', 'qualifications:delete', 'assignments:create', 'assignments:read', 'assignments:update', 'assignments:delete', 'experience:create', 'experience:read', 'experience:update', 'experience:delete', 'user-notifications:create', 'user-notifications:read', 'user-notifications:update', 'user-notifications:delete', 'missions:create', 'missions:read', 'missions:update', 'missions:delete', 'missions:read-sensitive', 'mission-attendances:create', 'mission-attendances:read', 'mission-attendances:update', 'mission-attendances:delete', 'campaigns:create', 'campaigns:read', 'campaigns:update', 'campaigns:delete', 'campaigns:manage', 'factions:create', 'factions:read', 'factions:update', 'factions:delete', 'technologies:create', 'technologies:read', 'technologies:update', 'technologies:delete', 'assets:create', 'assets:read', 'assets:update', 'assets:delete', 'resources:create', 'resources:read', 'resources:update', 'resources:delete', 'vehicles:create', 'vehicles:read', 'vehicles:update', 'vehicles:delete', 'structures:create', 'structures:read', 'structures:update', 'structures:delete', 'shipments:create', 'shipments:read', 'shipments:update', 'shipments:delete', 'bank-accounts:create', 'bank-accounts:read', 'bank-accounts:update', 'bank-accounts:delete', 'bank-transactions:create', 'bank-transactions:read', 'bank-transactions:update', 'bank-transactions:delete', 'ledger-entries:create', 'ledger-entries:read', 'ledger-entries:update', 'ledger-entries:delete', 'locker-storages:create', 'locker-storages:read', 'locker-storages:update', 'locker-storages:delete', 'loadouts:create', 'loadouts:read', 'loadouts:update', 'loadouts:delete', 'market-listings:create', 'market-listings:read', 'market-listings:update', 'market-listings:delete', 'market-negotiations:create', 'market-negotiations:read', 'market-negotiations:update', 'market-negotiations:delete', 'projects:create', 'projects:read', 'projects:update', 'projects:delete', 'labels:create', 'labels:read', 'labels:update', 'labels:delete', 'sprints:create', 'sprints:read', 'sprints:update', 'sprints:delete', 'releases:create', 'releases:read', 'releases:update', 'releases:delete', 'tickets:create', 'tickets:read', 'tickets:update', 'tickets:delete', 'game-structures:create', 'game-structures:read', 'game-structures:update', 'game-structures:delete', 'game-vehicles:create', 'game-vehicles:read', 'game-vehicles:update', 'game-vehicles:delete', 'game-npcs:create', 'game-npcs:read', 'game-npcs:update', 'game-npcs:delete', 'game-hard-resources:create', 'game-hard-resources:read', 'game-hard-resources:update', 'game-hard-resources:delete', 'game-event-logs:create', 'game-event-logs:read', 'game-event-logs:update', 'game-event-logs:delete', 'maps:create', 'maps:read', 'maps:update', 'maps:delete', 'narrative-events:create', 'narrative-events:read', 'narrative-events:update', 'narrative-events:delete', 'mission-files:create', 'mission-files:read', 'mission-files:update', 'mission-files:delete', 'mod-lists:create', 'mod-lists:read', 'mod-lists:update', 'mod-lists:delete', 'game-rules:read', 'game-rules:update', 'shims:read', 'shims:update', 'logistics:manage', 'intelligence:manage', 'profiles:intel_excerpt:update', 'banking:manage', 'tickets:staff', 'discord:staff', 'discord:announce');
ALTER TABLE "roles_permissions" ALTER COLUMN "value" SET DATA TYPE "public"."enum_roles_permissions" USING "value"::"public"."enum_roles_permissions";
DROP INDEX "payload_locked_documents_rels_evaluations_id_idx";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "evaluations_id";
DROP TYPE "public"."enum_evaluations_kind";
DROP TYPE "public"."enum_evaluations_level";`)
}