diff --git a/drizzle/0014_daffy_genesis.sql b/drizzle/0014_daffy_genesis.sql new file mode 100644 index 0000000..6421371 --- /dev/null +++ b/drizzle/0014_daffy_genesis.sql @@ -0,0 +1,14 @@ +CREATE TYPE "public"."legion_action" AS ENUM('mark_for_printing', 'unmark_for_printing', 'print', 'reject', 'already_printed');--> statement-breakpoint +CREATE TABLE "legion_review" ( + "id" serial PRIMARY KEY NOT NULL, + "userId" integer NOT NULL, + "projectId" integer NOT NULL, + "feedback" text, + "notes" text, + "filamentUsed" real, + "action" "legion_action" NOT NULL, + "timestamp" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "legion_review" ADD CONSTRAINT "legion_review_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "legion_review" ADD CONSTRAINT "legion_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/0015_flaky_james_howlett.sql b/drizzle/0015_flaky_james_howlett.sql new file mode 100644 index 0000000..f77d4d9 --- /dev/null +++ b/drizzle/0015_flaky_james_howlett.sql @@ -0,0 +1 @@ +ALTER TYPE "public"."legion_action" ADD VALUE 'add_comment' BEFORE 'reject'; \ No newline at end of file diff --git a/drizzle/meta/0014_snapshot.json b/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..5350277 --- /dev/null +++ b/drizzle/meta/0014_snapshot.json @@ -0,0 +1,888 @@ +{ + "id": "a3653c48-2bf5-403b-80fe-ecd6a3a1744e", + "prevId": "29b99a55-01b3-4065-8346-2bc545858893", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.devlog": { + "name": "devlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timeSpent": { + "name": "timeSpent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "devlog_userId_user_id_fk": { + "name": "devlog_userId_user_id_fk", + "tableFrom": "devlog", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "devlog_projectId_project_id_fk": { + "name": "devlog_projectId_project_id_fk", + "tableFrom": "devlog", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legion_review": { + "name": "legion_review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filamentUsed": { + "name": "filamentUsed", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "legion_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "legion_review_userId_user_id_fk": { + "name": "legion_review_userId_user_id_fk", + "tableFrom": "legion_review", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "legion_review_projectId_project_id_fk": { + "name": "legion_review_projectId_project_id_fk", + "tableFrom": "legion_review", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editorFileType": { + "name": "editorFileType", + "type": "editor_file_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "editorUrl": { + "name": "editorUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploadedFileUrl": { + "name": "uploadedFileUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modelFile": { + "name": "modelFile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'building'" + }, + "printedBy": { + "name": "printedBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "submittedToAirtable": { + "name": "submittedToAirtable", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "project_userId_user_id_fk": { + "name": "project_userId_user_id_fk", + "tableFrom": "project", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_printedBy_user_id_fk": { + "name": "project_printedBy_user_id_fk", + "tableFrom": "project", + "tableTo": "user", + "columnsFrom": [ + "printedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ship": { + "name": "ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "editorFileType": { + "name": "editorFileType", + "type": "editor_file_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "editorUrl": { + "name": "editorUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploadedFileUrl": { + "name": "uploadedFileUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modelFile": { + "name": "modelFile", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ship_userId_user_id_fk": { + "name": "ship_userId_user_id_fk", + "tableFrom": "ship", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ship_projectId_project_id_fk": { + "name": "ship_projectId_project_id_fk", + "tableFrom": "ship", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.t1_review": { + "name": "t1_review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "t1_review_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "t1_review_userId_user_id_fk": { + "name": "t1_review_userId_user_id_fk", + "tableFrom": "t1_review", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "t1_review_projectId_project_id_fk": { + "name": "t1_review_projectId_project_id_fk", + "tableFrom": "t1_review", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.t2_review": { + "name": "t2_review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "currencyMultiplier": { + "name": "currencyMultiplier", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "t2_review_userId_user_id_fk": { + "name": "t2_review_userId_user_id_fk", + "tableFrom": "t2_review", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "t2_review_projectId_project_id_fk": { + "name": "t2_review_projectId_project_id_fk", + "tableFrom": "t2_review", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "idvId": { + "name": "idvId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idvToken": { + "name": "idvToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profilePicture": { + "name": "profilePicture", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hackatimeTrust": { + "name": "hackatimeTrust", + "type": "hackatime_trust", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trust": { + "name": "trust", + "type": "trust", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'blue'" + }, + "clay": { + "name": "clay", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "brick": { + "name": "brick", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "shopScore": { + "name": "shopScore", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "hasBasePrinter": { + "name": "hasBasePrinter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasT1Review": { + "name": "hasT1Review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasT2Review": { + "name": "hasT2Review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasAdmin": { + "name": "hasAdmin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isPrinter": { + "name": "isPrinter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastLoginAt": { + "name": "lastLoginAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_idvId_unique": { + "name": "user_idvId_unique", + "nullsNotDistinct": false, + "columns": [ + "idvId" + ] + }, + "user_slackId_unique": { + "name": "user_slackId_unique", + "nullsNotDistinct": false, + "columns": [ + "slackId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.editor_file_type": { + "name": "editor_file_type", + "schema": "public", + "values": [ + "url", + "upload" + ] + }, + "public.hackatime_trust": { + "name": "hackatime_trust", + "schema": "public", + "values": [ + "green", + "blue", + "yellow", + "red" + ] + }, + "public.legion_action": { + "name": "legion_action", + "schema": "public", + "values": [ + "mark_for_printing", + "unmark_for_printing", + "print", + "reject", + "already_printed" + ] + }, + "public.project_audit_log_type": { + "name": "project_audit_log_type", + "schema": "public", + "values": [ + "create", + "update", + "delete" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "building", + "submitted", + "t1_approved", + "printing", + "printed", + "t2_approved", + "finalized", + "rejected", + "rejected_locked" + ] + }, + "public.t1_review_action": { + "name": "t1_review_action", + "schema": "public", + "values": [ + "approve", + "approve_no_print", + "add_comment", + "reject", + "reject_lock" + ] + }, + "public.trust": { + "name": "trust", + "schema": "public", + "values": [ + "green", + "blue", + "yellow", + "red" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0015_snapshot.json b/drizzle/meta/0015_snapshot.json new file mode 100644 index 0000000..a283243 --- /dev/null +++ b/drizzle/meta/0015_snapshot.json @@ -0,0 +1,889 @@ +{ + "id": "1299d59c-43b6-46a5-9557-3c51b87eac07", + "prevId": "a3653c48-2bf5-403b-80fe-ecd6a3a1744e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.devlog": { + "name": "devlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timeSpent": { + "name": "timeSpent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "devlog_userId_user_id_fk": { + "name": "devlog_userId_user_id_fk", + "tableFrom": "devlog", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "devlog_projectId_project_id_fk": { + "name": "devlog_projectId_project_id_fk", + "tableFrom": "devlog", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legion_review": { + "name": "legion_review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filamentUsed": { + "name": "filamentUsed", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "legion_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "legion_review_userId_user_id_fk": { + "name": "legion_review_userId_user_id_fk", + "tableFrom": "legion_review", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "legion_review_projectId_project_id_fk": { + "name": "legion_review_projectId_project_id_fk", + "tableFrom": "legion_review", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "editorFileType": { + "name": "editorFileType", + "type": "editor_file_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "editorUrl": { + "name": "editorUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploadedFileUrl": { + "name": "uploadedFileUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modelFile": { + "name": "modelFile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'building'" + }, + "printedBy": { + "name": "printedBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "submittedToAirtable": { + "name": "submittedToAirtable", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "project_userId_user_id_fk": { + "name": "project_userId_user_id_fk", + "tableFrom": "project", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_printedBy_user_id_fk": { + "name": "project_printedBy_user_id_fk", + "tableFrom": "project", + "tableTo": "user", + "columnsFrom": [ + "printedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ship": { + "name": "ship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "editorFileType": { + "name": "editorFileType", + "type": "editor_file_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "editorUrl": { + "name": "editorUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploadedFileUrl": { + "name": "uploadedFileUrl", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modelFile": { + "name": "modelFile", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ship_userId_user_id_fk": { + "name": "ship_userId_user_id_fk", + "tableFrom": "ship", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ship_projectId_project_id_fk": { + "name": "ship_projectId_project_id_fk", + "tableFrom": "ship", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.t1_review": { + "name": "t1_review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "t1_review_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "t1_review_userId_user_id_fk": { + "name": "t1_review_userId_user_id_fk", + "tableFrom": "t1_review", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "t1_review_projectId_project_id_fk": { + "name": "t1_review_projectId_project_id_fk", + "tableFrom": "t1_review", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.t2_review": { + "name": "t2_review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "projectId": { + "name": "projectId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "currencyMultiplier": { + "name": "currencyMultiplier", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "t2_review_userId_user_id_fk": { + "name": "t2_review_userId_user_id_fk", + "tableFrom": "t2_review", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "t2_review_projectId_project_id_fk": { + "name": "t2_review_projectId_project_id_fk", + "tableFrom": "t2_review", + "tableTo": "project", + "columnsFrom": [ + "projectId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "idvId": { + "name": "idvId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idvToken": { + "name": "idvToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slackId": { + "name": "slackId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profilePicture": { + "name": "profilePicture", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hackatimeTrust": { + "name": "hackatimeTrust", + "type": "hackatime_trust", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trust": { + "name": "trust", + "type": "trust", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'blue'" + }, + "clay": { + "name": "clay", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "brick": { + "name": "brick", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "shopScore": { + "name": "shopScore", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "hasBasePrinter": { + "name": "hasBasePrinter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasT1Review": { + "name": "hasT1Review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasT2Review": { + "name": "hasT2Review", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasAdmin": { + "name": "hasAdmin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "isPrinter": { + "name": "isPrinter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastLoginAt": { + "name": "lastLoginAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_idvId_unique": { + "name": "user_idvId_unique", + "nullsNotDistinct": false, + "columns": [ + "idvId" + ] + }, + "user_slackId_unique": { + "name": "user_slackId_unique", + "nullsNotDistinct": false, + "columns": [ + "slackId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.editor_file_type": { + "name": "editor_file_type", + "schema": "public", + "values": [ + "url", + "upload" + ] + }, + "public.hackatime_trust": { + "name": "hackatime_trust", + "schema": "public", + "values": [ + "green", + "blue", + "yellow", + "red" + ] + }, + "public.legion_action": { + "name": "legion_action", + "schema": "public", + "values": [ + "mark_for_printing", + "unmark_for_printing", + "print", + "add_comment", + "reject", + "already_printed" + ] + }, + "public.project_audit_log_type": { + "name": "project_audit_log_type", + "schema": "public", + "values": [ + "create", + "update", + "delete" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "building", + "submitted", + "t1_approved", + "printing", + "printed", + "t2_approved", + "finalized", + "rejected", + "rejected_locked" + ] + }, + "public.t1_review_action": { + "name": "t1_review_action", + "schema": "public", + "values": [ + "approve", + "approve_no_print", + "add_comment", + "reject", + "reject_lock" + ] + }, + "public.trust": { + "name": "trust", + "schema": "public", + "values": [ + "green", + "blue", + "yellow", + "red" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index fd2e7db..a7d9ba3 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -99,6 +99,20 @@ "when": 1766012636210, "tag": "0013_lying_donald_blake", "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1766163164021, + "tag": "0014_daffy_genesis", + "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1766163214868, + "tag": "0015_flaky_james_howlett", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index fb3eecd..186e960 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -143,6 +143,33 @@ export const t1Review = pgTable('t1_review', { timestamp: timestamp().notNull().defaultNow() }); +export const legionActionEnum = pgEnum('legion_action', [ + 'mark_for_printing', + 'unmark_for_printing', + 'print', + 'add_comment', + 'reject', + 'already_printed' +]); + +// Actions done by legion +export const legionReview = pgTable('legion_review', { + id: serial().primaryKey(), + userId: integer() + .notNull() + .references(() => user.id), + projectId: integer() + .notNull() + .references(() => project.id), + + feedback: text(), + notes: text(), + filamentUsed: real(), + action: legionActionEnum().notNull(), + + timestamp: timestamp().notNull().defaultNow() +}); + export const t2Review = pgTable('t2_review', { id: serial().primaryKey(), userId: integer() @@ -181,4 +208,5 @@ export type User = typeof user.$inferSelect; export type Project = typeof project.$inferSelect; export type T1Review = typeof t1Review.$inferSelect; -// export type T2Review = typeof t2Review.$inferSelect; +export type LegionReview = typeof legionReview.$inferSelect; +export type T2Review = typeof t2Review.$inferSelect; diff --git a/src/routes/dashboard/admin/ReviewHistory.svelte b/src/routes/dashboard/admin/ReviewHistory.svelte new file mode 100644 index 0000000..5562e77 --- /dev/null +++ b/src/routes/dashboard/admin/ReviewHistory.svelte @@ -0,0 +1,37 @@ + + +
+

T1 reviews

+ {#each reviews.t1Reviews as review} +
+

{review.action}

+

Notes: {review.notes}

+

Feedback: {review.feedback}

+

+ reviewed by {review.user.name} +

+
+ {/each} +

Legion actions

+ {#each reviews.legionReviews as review} +
+

{review.action}

+ {#if review.notes} +

Notes: {review.notes}

+ {/if} + {#if review.feedback} +

Feedback: {review.feedback}

+ {/if} + {#if review.filamentUsed} +

Filament used: {review.filamentUsed}g

+ {/if} +

+ by {review.user.name} +

+
+ {/each} +
diff --git a/src/routes/dashboard/admin/getReviewHistory.server.ts b/src/routes/dashboard/admin/getReviewHistory.server.ts new file mode 100644 index 0000000..6bf3750 --- /dev/null +++ b/src/routes/dashboard/admin/getReviewHistory.server.ts @@ -0,0 +1,43 @@ +import { db } from '$lib/server/db'; +import { legionReview, t1Review, user } from '$lib/server/db/schema'; +import { eq, asc } from 'drizzle-orm'; + +export async function getReviewHistory(id: number) { + const t1Reviews = await db + .select({ + user: { + id: user.id, + name: user.name + }, + action: t1Review.action, + notes: t1Review.notes, + feedback: t1Review.feedback, + timestamp: t1Review.timestamp + }) + .from(t1Review) + .innerJoin(user, eq(user.id, t1Review.userId)) + .where(eq(t1Review.projectId, id)) + .orderBy(asc(t1Review.timestamp)); + + const legionReviews = await db + .select({ + user: { + id: user.id, + name: user.name + }, + action: legionReview.action, + filamentUsed: legionReview.filamentUsed, + notes: legionReview.notes, + feedback: legionReview.feedback, + timestamp: legionReview.timestamp + }) + .from(legionReview) + .innerJoin(user, eq(user.id, legionReview.userId)) + .where(eq(legionReview.projectId, id)) + .orderBy(asc(legionReview.timestamp)); + + return { + t1Reviews, + legionReviews + }; +} diff --git a/src/routes/dashboard/admin/print/+page.server.ts b/src/routes/dashboard/admin/print/+page.server.ts index b452f61..f246422 100644 --- a/src/routes/dashboard/admin/print/+page.server.ts +++ b/src/routes/dashboard/admin/print/+page.server.ts @@ -3,7 +3,7 @@ import { project, user, devlog } from '$lib/server/db/schema.js'; import { error } from '@sveltejs/kit'; import { eq, and, sql, ne, inArray } from 'drizzle-orm'; import type { Actions } from './$types'; -import { getCurrentlyPrinting } from './utils'; +import { getCurrentlyPrinting } from './utils.server'; export async function load({ locals }) { if (!locals.user) { @@ -31,7 +31,7 @@ export async function load({ locals }) { .from(user) .where(and(ne(user.trust, 'red'), ne(user.hackatimeTrust, 'red'))); // hide banned users - const currentlyPrinting = getCurrentlyPrinting(locals.user); + const currentlyPrinting = await getCurrentlyPrinting(locals.user); return { allProjects, diff --git a/src/routes/dashboard/admin/print/+page.svelte b/src/routes/dashboard/admin/print/+page.svelte index 4604093..fc8301f 100644 --- a/src/routes/dashboard/admin/print/+page.svelte +++ b/src/routes/dashboard/admin/print/+page.svelte @@ -2,7 +2,7 @@ import { enhance } from '$app/forms'; import Head from '$lib/components/Head.svelte'; import { projectStatuses } from '$lib/utils.js'; - import { ExternalLink } from '@lucide/svelte'; + import { ExternalLink, Info } from '@lucide/svelte'; import relativeDate from 'tiny-relative-date'; let { data, form } = $props(); @@ -29,6 +29,18 @@

Print

+ {#if data.currentlyPrinting} +
+ +

+ You've already marked a project as printing: + {data.currentlyPrinting.name} +

+
+ {/if} +

Filter & Sort

diff --git a/src/routes/dashboard/admin/print/[id]/+page.server.ts b/src/routes/dashboard/admin/print/[id]/+page.server.ts index 4058e7e..8eb27a6 100644 --- a/src/routes/dashboard/admin/print/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/print/[id]/+page.server.ts @@ -1,8 +1,11 @@ import { db } from '$lib/server/db/index.js'; -import { project, user, devlog, t1Review } from '$lib/server/db/schema.js'; -import { error, redirect } from '@sveltejs/kit'; +import { project, user, devlog, legionReview } from '$lib/server/db/schema.js'; +import { error } from '@sveltejs/kit'; import { eq, and, asc, sql } from 'drizzle-orm'; import type { Actions } from './$types'; +import { sendSlackDM } from '$lib/server/slack.js'; +import { getReviewHistory } from '../../getReviewHistory.server'; +import { getCurrentlyPrinting } from '../utils.server'; export async function load({ locals, params }) { if (!locals.user) { @@ -20,7 +23,15 @@ export async function load({ locals, params }) { id: project.id, name: project.name, description: project.description, + url: project.url, + editorFileType: project.editorFileType, + editorUrl: project.editorUrl, + uploadedFileUrl: project.uploadedFileUrl, + modelFile: project.modelFile, + + printedBy: project.printedBy, + createdAt: project.createdAt, updatedAt: project.updatedAt, status: project.status @@ -44,6 +55,11 @@ export async function load({ locals, params }) { project.name, project.description, project.url, + project.editorFileType, + project.editorUrl, + project.uploadedFileUrl, + project.modelFile, + project.printedBy, project.createdAt, project.status, user.id, @@ -64,32 +80,18 @@ export async function load({ locals, params }) { .where(and(eq(devlog.projectId, queriedProject.project.id), eq(devlog.deleted, false))) .orderBy(asc(devlog.createdAt)); - const t1Reviews = await db - .select({ - user: { - id: user.id, - name: user.name - }, - action: t1Review.action, - notes: t1Review.notes, - feedback: t1Review.feedback, - timestamp: t1Review.timestamp - }) - .from(t1Review) - .innerJoin(user, eq(user.id, t1Review.userId)) - .where(eq(t1Review.projectId, queriedProject.project.id)) - // .groupBy(t1Review.userId, t1Review.notes, t1Review.feedback, t1Review.timestamp) - .orderBy(asc(t1Review.timestamp)); + const currentlyPrinting = await getCurrentlyPrinting(locals.user); return { project: queriedProject, devlogs, - t1Reviews + reviews: await getReviewHistory(id), + currentlyPrinting }; } export const actions = { - default: async ({ locals, request, params }) => { + markForPrint: async ({ locals, params }) => { if (!locals.user) { throw error(500); } @@ -97,53 +99,231 @@ export const actions = { throw error(403, { message: 'get out, peasant' }); } + const currentlyPrinting = await getCurrentlyPrinting(locals.user); + const id: number = parseInt(params.id); + if (currentlyPrinting && currentlyPrinting.id !== id) { + return error(400, { + message: 'you are already printing something else right now' + }); + } + const [queriedProject] = await db .select({ - id: project.id + id: project.id, + status: project.status }) .from(project) - .where(and(eq(project.deleted, false), eq(project.id, id))); + .where(and(eq(project.id, id), eq(project.deleted, false))) + .limit(1); if (!queriedProject) { return error(404, { message: 'project not found' }); } + if (queriedProject.status !== 't1_approved') { + return error(403, { message: 'project is not marked as on print queue' }); + } + + await db.insert(legionReview).values({ + projectId: id, + userId: locals.user.id, + action: 'mark_for_printing' + }); + + await db + .update(project) + .set({ + status: 'printing', + printedBy: locals.user.id + }) + .where(eq(project.id, id)); + + return {}; + }, + + unmarkForPrint: async ({ locals, params }) => { + if (!locals.user) { + throw error(500); + } + if (!locals.user.isPrinter) { + throw error(403, { message: 'get out, peasant' }); + } + + const id: number = parseInt(params.id); + + const [queriedProject] = await db + .select({ + id: project.id, + status: project.status, + printedBy: project.printedBy + }) + .from(project) + .where(and(eq(project.id, id), eq(project.deleted, false))) + .limit(1); + + if (!queriedProject) { + return error(404, { message: 'project not found' }); + } + + if (queriedProject.status !== 'printing') { + return error(403, { message: 'project is not marked as printing' }); + } + + await db.insert(legionReview).values({ + projectId: id, + userId: locals.user.id, + action: 'unmark_for_printing' + }); + + await db + .update(project) + .set({ + status: 't1_approved', + printedBy: null + }) + .where(eq(project.id, id)); + + return {}; + }, + + print: async ({ locals, request, params }) => { + if (!locals.user) { + throw error(500); + } + if (!locals.user.isPrinter) { + throw error(403, { message: 'get out, peasant' }); + } + + const currentlyPrinting = await getCurrentlyPrinting(locals.user); + + const id: number = parseInt(params.id); + + if (!currentlyPrinting || currentlyPrinting.id !== id) { + return error(400, { + message: "you can only print a project if you've marked it as you're printing it" + }); + } + + const [queriedProject] = await db + .select({ + id: project.id, + status: project.status + }) + .from(project) + .where(and(eq(project.id, id), eq(project.deleted, false))) + .limit(1); + + if (!queriedProject) { + return error(404, { message: 'project not found' }); + } + + if (queriedProject.status !== 'printing') { + return error(403, { message: 'project is not marked as currently printing' }); + } + const data = await request.formData(); - const action = data.get('action') as typeof t1Review.action._.data; + const filamentUsed = data.get('filament'); const notes = data.get('notes')?.toString(); const feedback = data.get('feedback')?.toString(); - if (action === null || notes === null || feedback === null) { + if (notes === null || feedback === null) { return error(400); } - await db.insert(t1Review).values({ + if ( + !filamentUsed || + isNaN(parseFloat(filamentUsed.toString())) || + parseFloat(filamentUsed.toString()) < 0 + ) { + return error(400, { message: 'invalid filament used' }); + } + + await db.insert(legionReview).values({ projectId: id, userId: locals.user.id, - action, + filamentUsed: parseFloat(filamentUsed.toString()), notes, - feedback + feedback, + action: 'print' }); - let status: typeof project.status._.data | undefined = undefined; + await db + .update(project) + .set({ + status: 'printed' + }) + .where(eq(project.id, id)); + + // if (queriedProject.user) { + // const feedbackText = feedback ? `\n\nHere's what they said:\n${feedback}` : ''; + + // await sendSlackDM( + // queriedProject.user.slackId, + // `Your project has been ${statusMessage}${feedbackText}` + // ); + // } + + return {}; + }, + + action: async ({ locals, request, params }) => { + if (!locals.user) { + throw error(500); + } + if (!locals.user.isPrinter) { + throw error(403, { message: 'get out, peasant' }); + } + + const id: number = parseInt(params.id); + + const [queriedProject] = await db + .select({ + id: project.id, + status: project.status + }) + .from(project) + .where(and(eq(project.id, id), eq(project.deleted, false))) + .limit(1); + + if (!queriedProject) { + return error(404, { message: 'project not found' }); + } + + if (!['t1_approved', 'printing', 'printed'].includes(queriedProject.status)) { + return error(403, { message: 'project is not in the print stage' }); + } + + const data = await request.formData(); + const action = data.get('action')?.toString() as typeof legionReview.action._.data | undefined; + const notes = data.get('notes')?.toString(); + const feedback = data.get('feedback')?.toString(); + + if (notes === null || feedback === null || action === null || action === undefined) { + return error(400); + } + + let status: typeof project.status._.data; - switch (action) { - case 'approve': - status = 't1_approved'; - break; - case 'approve_no_print': - status = 'printed'; - break; - case 'reject': - status = 'rejected'; - break; - case 'reject_lock': - status = 'rejected_locked'; - break; + if (action === 'already_printed') { + status = 'printed'; + } else if (action === 'add_comment') { + status = queriedProject.status; + } else if (action === 'reject') { + status = 'submitted'; + } else { + return error(400); } + await db.insert(legionReview).values({ + projectId: id, + userId: locals.user.id, + notes, + feedback, + action + }); + await db .update(project) .set({ @@ -151,6 +331,6 @@ export const actions = { }) .where(eq(project.id, id)); - return redirect(302, '/dashboard/admin/print'); + return {}; } } satisfies Actions; diff --git a/src/routes/dashboard/admin/print/[id]/+page.svelte b/src/routes/dashboard/admin/print/[id]/+page.svelte index 98e0a20..cf8ad38 100644 --- a/src/routes/dashboard/admin/print/[id]/+page.svelte +++ b/src/routes/dashboard/admin/print/[id]/+page.svelte @@ -2,18 +2,26 @@ import relativeDate from 'tiny-relative-date'; import Devlog from '$lib/components/Devlog.svelte'; import Head from '$lib/components/Head.svelte'; - import { ExternalLink } from '@lucide/svelte'; import { enhance } from '$app/forms'; import { projectStatuses } from '$lib/utils.js'; + import ProjectLinks from '$lib/components/ProjectLinks.svelte'; + import Spinny3DPreview from '$lib/components/Spinny3DPreview.svelte'; + import { Download } from '@lucide/svelte'; + import ReviewHistory from '../../ReviewHistory.svelte'; - let { data } = $props(); + let { data, form } = $props(); - let formPending = $state(false); + let actionsFormPending = $state(false); + let printFormPending = $state(false); + let markForPrintPending = $state(false); + let unmarkForPrintPending = $state(false); -
+

{data.project.project.name}

@@ -36,15 +44,13 @@ .project.timeSpent % 60}min

Status: {projectStatuses[data.project.project.status]}

-
- {#if data.project.project.url && data.project.project.url.length > 0} - - - Printables page - - {:else} -

No Printables link

- {/if} +
+
@@ -74,53 +80,182 @@

Description

-

{data.project.project.description}

+

+ {#each data.project.project.description?.split('\n') as descriptionSection} + {descriptionSection} +
+ {/each} +

-

Review

-
-
{ - formPending = true; - return async ({ update }) => { - await update(); - formPending = false; - }; - }} - onsubmit={() => { - return confirm('really submit?'); - }} - > - - - - - - - -
-
+ {#if data.project.project.modelFile} +
+

3D model

+ + + +
+ +
+ +
+ {/if} + +

Printering area

+ + {#if (data.project.project.status === 't1_approved' && !data.currentlyPrinting) || (data.project.project.status === 'printing' && data.project.project.printedBy === data.user.id)} +
+ {#if data.project.project.status === 't1_approved' && !data.currentlyPrinting} +
{ + markForPrintPending = true; + return async ({ update }) => { + await update({ reset: false }); + markForPrintPending = false; + }; + }} + > + +
+ {/if} + {#if data.project.project.status === 'printing' && data.project.project.printedBy === data.user.id} +
{ + unmarkForPrintPending = true; + return async ({ update }) => { + await update({ reset: false }); + unmarkForPrintPending = false; + }; + }} + > + +
+ {/if} +
+ {/if} + + {#if data.project.project.status === 'printing' && data.project.project.printedBy === data.user.id} +

Print

+
+
{ + printFormPending = true; + return async ({ update }) => { + await update({ reset: false }); + printFormPending = false; + }; + }} + onsubmit={() => { + return confirm('really submit?'); + }} + > + + + + + + + + + +
+
+ {/if} + + {#if ['t1_approved', 'printing', 'printed'].includes(data.project.project.status)} +

Other stuff

+
+
{ + actionsFormPending = true; + return async ({ update }) => { + await update({ reset: false }); + actionsFormPending = false; + }; + }} + onsubmit={() => { + return confirm('really submit?'); + }} + > + + + + + + + + +
+
+ {/if}

Journal logs

@@ -130,21 +265,7 @@
-
-
-

Review history

- {#each data.t1Reviews as review} -
-

{review.action}

-

Notes: {review.notes}

-

Feedback: {review.feedback}

-

- reviewed by {review.user.name} -

-
- {/each} -
+
+
diff --git a/src/routes/dashboard/admin/print/utils.server.ts b/src/routes/dashboard/admin/print/utils.server.ts new file mode 100644 index 0000000..ac78a1a --- /dev/null +++ b/src/routes/dashboard/admin/print/utils.server.ts @@ -0,0 +1,22 @@ +import { db } from '$lib/server/db'; +import { project } from '$lib/server/db/schema'; +import { and, eq, type SQLWrapper } from 'drizzle-orm'; + +export async function getCurrentlyPrinting(user: { id: number | SQLWrapper }) { + const [currentlyPrinting] = await db + .select({ + id: project.id, + name: project.name + }) + .from(project) + .where( + and( + eq(project.printedBy, user.id), + eq(project.status, 'printing'), + eq(project.deleted, false) + ) + ) + .limit(1); + + return currentlyPrinting; +} diff --git a/src/routes/dashboard/admin/print/utils.ts b/src/routes/dashboard/admin/print/utils.ts deleted file mode 100644 index f17b090..0000000 --- a/src/routes/dashboard/admin/print/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { db } from "$lib/server/db"; -import { project } from "$lib/server/db/schema"; -import { and, eq, type SQLWrapper } from "drizzle-orm"; - -export async function getCurrentlyPrinting(user: { id: number | SQLWrapper; }) { - const [currentlyPrinting] = await db - .select() - .from(project) - .where( - and( - eq(project.printedBy, user.id), - eq(project.status, 'printing'), - eq(project.deleted, false) - ) - ) - .limit(1); - - return currentlyPrinting; -} \ No newline at end of file diff --git a/src/routes/dashboard/admin/review/[id]/+page.server.ts b/src/routes/dashboard/admin/review/[id]/+page.server.ts index 64bfa2c..78d4594 100644 --- a/src/routes/dashboard/admin/review/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/review/[id]/+page.server.ts @@ -4,6 +4,7 @@ import { error, redirect } from '@sveltejs/kit'; import { eq, and, asc, sql } from 'drizzle-orm'; import type { Actions } from './$types'; import { sendSlackDM } from '$lib/server/slack.js'; +import { getReviewHistory } from '../../getReviewHistory.server'; export async function load({ locals, params }) { if (!locals.user) { @@ -75,27 +76,10 @@ export async function load({ locals, params }) { .where(and(eq(devlog.projectId, queriedProject.project.id), eq(devlog.deleted, false))) .orderBy(asc(devlog.createdAt)); - const t1Reviews = await db - .select({ - user: { - id: user.id, - name: user.name - }, - action: t1Review.action, - notes: t1Review.notes, - feedback: t1Review.feedback, - timestamp: t1Review.timestamp - }) - .from(t1Review) - .innerJoin(user, eq(user.id, t1Review.userId)) - .where(eq(t1Review.projectId, queriedProject.project.id)) - // .groupBy(t1Review.userId, t1Review.notes, t1Review.feedback, t1Review.timestamp) - .orderBy(asc(t1Review.timestamp)); - return { project: queriedProject, devlogs, - t1Reviews + reviews: await getReviewHistory(id) }; } diff --git a/src/routes/dashboard/admin/review/[id]/+page.svelte b/src/routes/dashboard/admin/review/[id]/+page.svelte index 065311e..e7206f5 100644 --- a/src/routes/dashboard/admin/review/[id]/+page.svelte +++ b/src/routes/dashboard/admin/review/[id]/+page.svelte @@ -7,6 +7,7 @@ import ProjectLinks from '$lib/components/ProjectLinks.svelte'; import Spinny3DPreview from '$lib/components/Spinny3DPreview.svelte'; import { Download } from '@lucide/svelte'; + import ReviewHistory from '../../ReviewHistory.svelte'; let { data } = $props(); @@ -142,8 +143,8 @@ @@ -163,20 +164,6 @@
-
-

Review history

- {#each data.t1Reviews as review} -
-

{review.action}

-

Notes: {review.notes}

-

Feedback: {review.feedback}

-

- reviewed by {review.user.name} -

-
- {/each} -
+
diff --git a/src/routes/dashboard/admin/ysws-review/[id]/+page.server.ts b/src/routes/dashboard/admin/ysws-review/[id]/+page.server.ts index 3739949..29f4e69 100644 --- a/src/routes/dashboard/admin/ysws-review/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/ysws-review/[id]/+page.server.ts @@ -1,5 +1,5 @@ import { db } from '$lib/server/db/index.js'; -import { project, user, devlog, t1Review, t2Review } from '$lib/server/db/schema.js'; +import { project, user, devlog, t2Review } from '$lib/server/db/schema.js'; import { error, fail, redirect } from '@sveltejs/kit'; import { eq, and, asc, sql, desc } from 'drizzle-orm'; import type { Actions } from './$types'; @@ -8,6 +8,7 @@ import { airtableBase } from '$lib/server/airtable'; import { env } from '$env/dynamic/private'; import { decrypt } from '$lib/server/encryption'; import { getUserData } from '$lib/server/idvUserData'; +import { getReviewHistory } from '../../getReviewHistory.server'; export async function load({ locals, params }) { if (!locals.user) { @@ -82,27 +83,10 @@ export async function load({ locals, params }) { .where(and(eq(devlog.projectId, queriedProject.project.id), eq(devlog.deleted, false))) .orderBy(asc(devlog.createdAt)); - const t1Reviews = await db - .select({ - user: { - id: user.id, - name: user.name - }, - action: t1Review.action, - notes: t1Review.notes, - feedback: t1Review.feedback, - timestamp: t1Review.timestamp - }) - .from(t1Review) - .innerJoin(user, eq(user.id, t1Review.userId)) - .where(eq(t1Review.projectId, queriedProject.project.id)) - // .groupBy(t1Review.userId, t1Review.notes, t1Review.feedback, t1Review.timestamp) - .orderBy(asc(t1Review.timestamp)); - return { project: queriedProject, devlogs, - t1Reviews + reviews: await getReviewHistory(id) }; } diff --git a/src/routes/dashboard/admin/ysws-review/[id]/+page.svelte b/src/routes/dashboard/admin/ysws-review/[id]/+page.svelte index f1cd43a..82a7cfd 100644 --- a/src/routes/dashboard/admin/ysws-review/[id]/+page.svelte +++ b/src/routes/dashboard/admin/ysws-review/[id]/+page.svelte @@ -7,6 +7,7 @@ import ProjectLinks from '$lib/components/ProjectLinks.svelte'; import Spinny3DPreview from '$lib/components/Spinny3DPreview.svelte'; import { Download } from '@lucide/svelte'; + import ReviewHistory from '../../ReviewHistory.svelte'; let { data, form } = $props(); @@ -107,7 +108,9 @@
{/if} -

YSWS Review (don't use yet if you're not Arca)

+

+ YSWS Review (currently you need to add currency/market score manually) +

-
-

T1 review history

- {#each data.t1Reviews as review} -
-

{review.action}

-

Notes: {review.notes}

-

Feedback: {review.feedback}

-

- reviewed by {review.user.name} -

-
- {/each} -
+
diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index 683cb6c..5bfdd80 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -7,7 +7,7 @@ let timeLeft = $state({ days: 0, hours: 0, minutes: 0, seconds: 0 }); let interval: ReturnType; - const targetDate = new Date('2025-12-19T23:59:00-05:00').getTime(); + const targetDate = new Date('2025-12-24T23:59:00-05:00').getTime(); function updateTimer() { const now = new Date().getTime();