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:
parent
9955c376f9
commit
15145f613a
3 changed files with 19481 additions and 0 deletions
88
src/collections/users/Evaluations.ts
Normal file
88
src/collections/users/Evaluations.ts
Normal 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.",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
19328
src/migrations/20260827_213857_add_evaluations_collection.json
Normal file
19328
src/migrations/20260827_213857_add_evaluations_collection.json
Normal file
File diff suppressed because it is too large
Load diff
65
src/migrations/20260827_213857_add_evaluations_collection.ts
Normal file
65
src/migrations/20260827_213857_add_evaluations_collection.ts
Normal 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";`)
|
||||
}
|
||||
Loading…
Reference in a new issue