From 908724822d78671d8ffa9cfe052fe5dbd5e1a5ae Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Mon, 22 Dec 2025 19:19:58 +0000 Subject: [PATCH 01/25] Create db schema for market items --- drizzle/0016_lazy_kinsey_walden.sql | 32 + drizzle/meta/0016_snapshot.json | 1099 +++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/server/db/schema.ts | 86 +++ 4 files changed, 1224 insertions(+) create mode 100644 drizzle/0016_lazy_kinsey_walden.sql create mode 100644 drizzle/meta/0016_snapshot.json diff --git a/drizzle/0016_lazy_kinsey_walden.sql b/drizzle/0016_lazy_kinsey_walden.sql new file mode 100644 index 0000000..4d1672d --- /dev/null +++ b/drizzle/0016_lazy_kinsey_walden.sql @@ -0,0 +1,32 @@ +CREATE TYPE "public"."market_order_status" AS ENUM('awaiting_approval', 'approved', 'fulfilled');--> statement-breakpoint +CREATE TABLE "market_item" ( + "id" serial PRIMARY KEY NOT NULL, + "createdBy" integer, + "name" text NOT NULL, + "description" text NOT NULL, + "image" text NOT NULL, + "minRequiredShopScore" integer DEFAULT 0 NOT NULL, + "minShopScore" integer NOT NULL, + "maxShopScore" integer NOT NULL, + "maxPrice" integer NOT NULL, + "minPrice" integer NOT NULL, + "isPublic" boolean DEFAULT false NOT NULL, + "deleted" boolean DEFAULT false NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "market_item_order" ( + "id" serial PRIMARY KEY NOT NULL, + "userId" integer, + "addressId" text NOT NULL, + "bricksPaid" integer NOT NULL, + "status" "market_order_status" DEFAULT 'awaiting_approval' NOT NULL, + "userNotes" text NOT NULL, + "notes" text NOT NULL, + "deleted" boolean DEFAULT false NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "market_item" ADD CONSTRAINT "market_item_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "market_item_order" ADD CONSTRAINT "market_item_order_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0016_snapshot.json b/drizzle/meta/0016_snapshot.json new file mode 100644 index 0000000..2f3871d --- /dev/null +++ b/drizzle/meta/0016_snapshot.json @@ -0,0 +1,1099 @@ +{ + "id": "af6c7146-421a-48ea-88a9-7abbde335bb7", + "prevId": "1299d59c-43b6-46a5-9557-3c51b87eac07", + "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.market_item": { + "name": "market_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "minRequiredShopScore": { + "name": "minRequiredShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "minShopScore": { + "name": "minShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxShopScore": { + "name": "maxShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxPrice": { + "name": "maxPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "minPrice": { + "name": "minPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "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": { + "market_item_createdBy_user_id_fk": { + "name": "market_item_createdBy_user_id_fk", + "tableFrom": "market_item", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.market_item_order": { + "name": "market_item_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "addressId": { + "name": "addressId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bricksPaid": { + "name": "bricksPaid", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "market_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "userNotes": { + "name": "userNotes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "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()" + } + }, + "indexes": {}, + "foreignKeys": { + "market_item_order_userId_user_id_fk": { + "name": "market_item_order_userId_user_id_fk", + "tableFrom": "market_item_order", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "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.market_order_status": { + "name": "market_order_status", + "schema": "public", + "values": [ + "awaiting_approval", + "approved", + "fulfilled" + ] + }, + "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 a7d9ba3..6f9c10f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1766163214868, "tag": "0015_flaky_james_howlett", "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1766430469939, + "tag": "0016_lazy_kinsey_walden", + "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 186e960..cc73478 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -29,6 +29,7 @@ export const user = pgTable('user', { shopScore: real().notNull().default(0), hasBasePrinter: boolean().notNull().default(false), + // bricksSpentOnUpgrades: integer().notNull().default(0), hasT1Review: boolean().notNull().default(false), // Has access to t1 review hasT2Review: boolean().notNull().default(false), // Has access to t2 review @@ -203,6 +204,89 @@ export const devlog = pgTable('devlog', { updatedAt: timestamp().notNull().defaultNow() }); +// Market + +export const marketItem = pgTable('market_item', { + id: serial().primaryKey(), + createdBy: integer().references(() => user.id), + + name: text().notNull(), + description: text().notNull(), + image: text().notNull(), + + minRequiredShopScore: integer().notNull().default(0), + + minShopScore: integer().notNull(), + maxShopScore: integer().notNull(), // Score after which price becomes constant + maxPrice: integer().notNull(), + minPrice: integer().notNull(), + + isPublic: boolean().notNull().default(false), + + deleted: boolean().notNull().default(false), + createdAt: timestamp().notNull().defaultNow(), + updatedAt: timestamp().notNull().defaultNow() +}); + +export const marketOrderStatus = pgEnum('market_order_status', [ + 'awaiting_approval', + 'approved', + 'fulfilled' +]); + +export const marketItemOrder = pgTable('market_item_order', { + id: serial().primaryKey(), + userId: integer().references(() => user.id), + + addressId: text().notNull(), + bricksPaid: integer().notNull(), + + status: marketOrderStatus().notNull().default('awaiting_approval'), + userNotes: text().notNull(), + notes: text().notNull(), + + deleted: boolean().notNull().default(false), + createdAt: timestamp().notNull().defaultNow() +}); + +// export const marketBasePrinter = pgTable('market_base_printer', { +// id: serial().primaryKey(), +// createdBy: integer().references(() => user.id), + +// name: text().notNull(), +// description: text().notNull(), +// image: text().notNull(), + +// minRequiredShopScore: integer().notNull().default(0), +// isPublic: boolean().notNull().default(false), + +// deleted: boolean().notNull().default(false), +// createdAt: timestamp().notNull().defaultNow(), +// updatedAt: timestamp().notNull().defaultNow() +// }); + +// export const marketPrinterUpgrade = pgTable('market_printer_upgrade', { +// id: serial().primaryKey(), +// createdBy: integer().references(() => user.id), + +// name: text().notNull(), +// description: text().notNull(), +// image: text().notNull(), + +// minRequiredShopScore: integer().notNull().default(0), + +// minShopScore: integer().notNull(), +// maxShopScore: integer().notNull(), // Score after which price becomes constant +// maxPrice: integer().notNull(), +// minPrice: integer().notNull(), + +// isPublic: boolean().notNull().default(false), + +// deleted: boolean().notNull().default(false), +// createdAt: timestamp().notNull().defaultNow(), +// updatedAt: timestamp().notNull().defaultNow() +// }); + export type Session = typeof session.$inferSelect; export type User = typeof user.$inferSelect; export type Project = typeof project.$inferSelect; @@ -210,3 +294,5 @@ export type Project = typeof project.$inferSelect; export type T1Review = typeof t1Review.$inferSelect; export type LegionReview = typeof legionReview.$inferSelect; export type T2Review = typeof t2Review.$inferSelect; + +export type MarketItem = typeof marketItem.$inferSelect; From 9988c397b2c2af63491dd643522fb3a38d4fa7c3 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Mon, 22 Dec 2025 20:04:51 +0000 Subject: [PATCH 02/25] Created market page backend --- src/routes/dashboard/market/+page.server.ts | 29 +++++++++ src/routes/dashboard/market/+page.svelte | 60 ++----------------- .../dashboard/market/MarketTimer.svelte | 57 ++++++++++++++++++ 3 files changed, 92 insertions(+), 54 deletions(-) create mode 100644 src/routes/dashboard/market/+page.server.ts create mode 100644 src/routes/dashboard/market/MarketTimer.svelte diff --git a/src/routes/dashboard/market/+page.server.ts b/src/routes/dashboard/market/+page.server.ts new file mode 100644 index 0000000..7d25d99 --- /dev/null +++ b/src/routes/dashboard/market/+page.server.ts @@ -0,0 +1,29 @@ +import { db } from '$lib/server/db/index.js'; +import { marketItem } from '$lib/server/db/schema.js'; +import { error } from '@sveltejs/kit'; +import { eq, and } from 'drizzle-orm'; + +export async function load({ locals }) { + if (!locals.user) { + throw error(500); + } + + const marketItems = await db + .select({ + id: marketItem.id, + name: marketItem.name, + description: marketItem.description, + image: marketItem.image + }) + .from(marketItem) + .where( + and( + eq(marketItem.deleted, false), + locals.user.hasAdmin ? undefined : eq(marketItem.isPublic, true) + ) + ); + + return { + marketItems + }; +} diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index 5bfdd80..547481c 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -1,66 +1,18 @@

Market

-
-

The Market Opens In

-
-
- {timeLeft.days} - DAYS -
-
- {timeLeft.hours} - HOURS -
-
- {timeLeft.minutes} - MINS -
-
- {timeLeft.seconds} - SECS -
-
-
+{#if data.marketItems.length === 0} + +{:else} +asdasd +{/if} diff --git a/src/routes/dashboard/market/MarketTimer.svelte b/src/routes/dashboard/market/MarketTimer.svelte new file mode 100644 index 0000000..9aa1e6c --- /dev/null +++ b/src/routes/dashboard/market/MarketTimer.svelte @@ -0,0 +1,57 @@ + + +
+

The Market Opens In

+
+
+ {timeLeft.days} + DAYS +
+
+ {timeLeft.hours} + HOURS +
+
+ {timeLeft.minutes} + MINS +
+
+ {timeLeft.seconds} + SECS +
+
+
\ No newline at end of file From f777d64a5fab4c97c84ce60453558441c213e56d Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Mon, 22 Dec 2025 21:16:55 +0000 Subject: [PATCH 03/25] Start creating admin page for market, change 403 error message --- drizzle/0017_long_greymalkin.sql | 2 + drizzle/meta/0017_snapshot.json | 1101 +++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/components/MarketItem.svelte | 16 + src/lib/server/db/schema.ts | 4 +- .../dashboard/admin/admin/+page.server.ts | 12 + src/routes/dashboard/admin/admin/+page.svelte | 11 +- .../admin/admin/market/+page.server.ts | 22 + .../dashboard/admin/admin/market/+page.svelte | 15 + .../admin/admin/stats/+page.server.ts | 2 +- .../admin/admin/users/+page.server.ts | 4 +- .../dashboard/admin/admin/users/+page.svelte | 2 +- .../admin/admin/users/[id]/+page.server.ts | 14 +- .../dashboard/admin/print/+page.server.ts | 4 +- .../admin/print/[id]/+page.server.ts | 10 +- .../dashboard/admin/review/+page.server.ts | 4 +- .../admin/review/[id]/+page.server.ts | 4 +- .../admin/ysws-review/+page.server.ts | 4 +- .../admin/ysws-review/[id]/+page.server.ts | 4 +- src/routes/dashboard/market/+page.svelte | 2 +- 20 files changed, 1215 insertions(+), 29 deletions(-) create mode 100644 drizzle/0017_long_greymalkin.sql create mode 100644 drizzle/meta/0017_snapshot.json create mode 100644 src/lib/components/MarketItem.svelte create mode 100644 src/routes/dashboard/admin/admin/+page.server.ts create mode 100644 src/routes/dashboard/admin/admin/market/+page.server.ts create mode 100644 src/routes/dashboard/admin/admin/market/+page.svelte diff --git a/drizzle/0017_long_greymalkin.sql b/drizzle/0017_long_greymalkin.sql new file mode 100644 index 0000000..a15f9d7 --- /dev/null +++ b/drizzle/0017_long_greymalkin.sql @@ -0,0 +1,2 @@ +ALTER TYPE "public"."market_order_status" ADD VALUE 'denied';--> statement-breakpoint +ALTER TYPE "public"."market_order_status" ADD VALUE 'refunded'; \ No newline at end of file diff --git a/drizzle/meta/0017_snapshot.json b/drizzle/meta/0017_snapshot.json new file mode 100644 index 0000000..fa2bb6e --- /dev/null +++ b/drizzle/meta/0017_snapshot.json @@ -0,0 +1,1101 @@ +{ + "id": "c1769f02-cca9-40e9-afe3-ed329a1f74c7", + "prevId": "af6c7146-421a-48ea-88a9-7abbde335bb7", + "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.market_item": { + "name": "market_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "minRequiredShopScore": { + "name": "minRequiredShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "minShopScore": { + "name": "minShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxShopScore": { + "name": "maxShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxPrice": { + "name": "maxPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "minPrice": { + "name": "minPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "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": { + "market_item_createdBy_user_id_fk": { + "name": "market_item_createdBy_user_id_fk", + "tableFrom": "market_item", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.market_item_order": { + "name": "market_item_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "addressId": { + "name": "addressId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bricksPaid": { + "name": "bricksPaid", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "market_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "userNotes": { + "name": "userNotes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "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()" + } + }, + "indexes": {}, + "foreignKeys": { + "market_item_order_userId_user_id_fk": { + "name": "market_item_order_userId_user_id_fk", + "tableFrom": "market_item_order", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "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.market_order_status": { + "name": "market_order_status", + "schema": "public", + "values": [ + "awaiting_approval", + "approved", + "fulfilled", + "denied", + "refunded" + ] + }, + "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 6f9c10f..14a627f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -120,6 +120,13 @@ "when": 1766430469939, "tag": "0016_lazy_kinsey_walden", "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1766436733390, + "tag": "0017_long_greymalkin", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/components/MarketItem.svelte b/src/lib/components/MarketItem.svelte new file mode 100644 index 0000000..e1f0283 --- /dev/null +++ b/src/lib/components/MarketItem.svelte @@ -0,0 +1,16 @@ + + +
+ {#if url} + + {/if} +
+ +
+
diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index cc73478..f5c2600 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -231,7 +231,9 @@ export const marketItem = pgTable('market_item', { export const marketOrderStatus = pgEnum('market_order_status', [ 'awaiting_approval', 'approved', - 'fulfilled' + 'fulfilled', + 'denied', + 'refunded' ]); export const marketItemOrder = pgTable('market_item_order', { diff --git a/src/routes/dashboard/admin/admin/+page.server.ts b/src/routes/dashboard/admin/admin/+page.server.ts new file mode 100644 index 0000000..f3c1446 --- /dev/null +++ b/src/routes/dashboard/admin/admin/+page.server.ts @@ -0,0 +1,12 @@ +import { error } from '@sveltejs/kit'; + +export async function load({ locals }) { + if (!locals.user) { + throw error(500); + } + if (!locals.user.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + return {}; +} diff --git a/src/routes/dashboard/admin/admin/+page.svelte b/src/routes/dashboard/admin/admin/+page.svelte index 3876834..06b91fc 100644 --- a/src/routes/dashboard/admin/admin/+page.svelte +++ b/src/routes/dashboard/admin/admin/+page.svelte @@ -1,6 +1,6 @@ @@ -29,5 +29,14 @@

Stats

+ +
+ +
+

Market

+
diff --git a/src/routes/dashboard/admin/admin/market/+page.server.ts b/src/routes/dashboard/admin/admin/market/+page.server.ts new file mode 100644 index 0000000..b941f82 --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/+page.server.ts @@ -0,0 +1,22 @@ +import { db } from '$lib/server/db/index.js'; +import { marketItem } from '$lib/server/db/schema.js'; +import { error } from '@sveltejs/kit'; +import { eq } from 'drizzle-orm'; + +export async function load({ locals }) { + if (!locals.user) { + throw error(500); + } + if (!locals.user.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const marketItems = await db + .select() + .from(marketItem) + .where(eq(marketItem.deleted, false)); + + return { + marketItems + }; +} diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte new file mode 100644 index 0000000..c1ec691 --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -0,0 +1,15 @@ + + + + +

Market

+ +
+ +
+ + diff --git a/src/routes/dashboard/admin/admin/stats/+page.server.ts b/src/routes/dashboard/admin/admin/stats/+page.server.ts index 2156bfe..09cbd08 100644 --- a/src/routes/dashboard/admin/admin/stats/+page.server.ts +++ b/src/routes/dashboard/admin/admin/stats/+page.server.ts @@ -8,7 +8,7 @@ export async function load({ locals }) { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const [users] = await db diff --git a/src/routes/dashboard/admin/admin/users/+page.server.ts b/src/routes/dashboard/admin/admin/users/+page.server.ts index 37403cb..e728b17 100644 --- a/src/routes/dashboard/admin/admin/users/+page.server.ts +++ b/src/routes/dashboard/admin/admin/users/+page.server.ts @@ -8,7 +8,7 @@ export async function load({ locals }) { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const users = await db.select().from(user); @@ -24,7 +24,7 @@ export const actions = { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } await db.delete(session); diff --git a/src/routes/dashboard/admin/admin/users/+page.svelte b/src/routes/dashboard/admin/admin/users/+page.svelte index 9d85401..455aab6 100644 --- a/src/routes/dashboard/admin/admin/users/+page.svelte +++ b/src/routes/dashboard/admin/admin/users/+page.svelte @@ -13,7 +13,7 @@ let logoutEveryonePending = $state(false); - +
diff --git a/src/routes/dashboard/admin/admin/users/[id]/+page.server.ts b/src/routes/dashboard/admin/admin/users/[id]/+page.server.ts index 0e277c6..554a98f 100644 --- a/src/routes/dashboard/admin/admin/users/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/admin/users/[id]/+page.server.ts @@ -18,7 +18,7 @@ export async function load({ locals, params }) { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -49,7 +49,7 @@ export const actions = { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -86,7 +86,7 @@ export const actions = { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -137,7 +137,7 @@ export const actions = { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -195,7 +195,7 @@ export const actions = { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -221,7 +221,7 @@ export const actions = { throw error(500); } if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -252,7 +252,7 @@ export const actions = { // Pretty important line if (!locals.user.hasAdmin) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); diff --git a/src/routes/dashboard/admin/print/+page.server.ts b/src/routes/dashboard/admin/print/+page.server.ts index f246422..3b91488 100644 --- a/src/routes/dashboard/admin/print/+page.server.ts +++ b/src/routes/dashboard/admin/print/+page.server.ts @@ -10,7 +10,7 @@ export async function load({ locals }) { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const projects = await getProjects(['t1_approved'], [], []); @@ -47,7 +47,7 @@ export const actions = { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const data = await request.formData(); diff --git a/src/routes/dashboard/admin/print/[id]/+page.server.ts b/src/routes/dashboard/admin/print/[id]/+page.server.ts index 8eb27a6..a59be0a 100644 --- a/src/routes/dashboard/admin/print/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/print/[id]/+page.server.ts @@ -12,7 +12,7 @@ export async function load({ locals, params }) { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -96,7 +96,7 @@ export const actions = { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const currentlyPrinting = await getCurrentlyPrinting(locals.user); @@ -148,7 +148,7 @@ export const actions = { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -193,7 +193,7 @@ export const actions = { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const currentlyPrinting = await getCurrentlyPrinting(locals.user); @@ -273,7 +273,7 @@ export const actions = { throw error(500); } if (!locals.user.isPrinter) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); diff --git a/src/routes/dashboard/admin/review/+page.server.ts b/src/routes/dashboard/admin/review/+page.server.ts index 97dcaec..1abf3bf 100644 --- a/src/routes/dashboard/admin/review/+page.server.ts +++ b/src/routes/dashboard/admin/review/+page.server.ts @@ -9,7 +9,7 @@ export async function load({ locals }) { throw error(500); } if (!locals.user.hasT1Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const projects = await getProjects(['submitted'], [], []); @@ -43,7 +43,7 @@ export const actions = { throw error(500); } if (!locals.user.hasT1Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const data = await request.formData(); diff --git a/src/routes/dashboard/admin/review/[id]/+page.server.ts b/src/routes/dashboard/admin/review/[id]/+page.server.ts index 78d4594..3250951 100644 --- a/src/routes/dashboard/admin/review/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/review/[id]/+page.server.ts @@ -11,7 +11,7 @@ export async function load({ locals, params }) { throw error(500); } if (!locals.user.hasT1Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -89,7 +89,7 @@ export const actions = { throw error(500); } if (!locals.user.hasT1Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); diff --git a/src/routes/dashboard/admin/ysws-review/+page.server.ts b/src/routes/dashboard/admin/ysws-review/+page.server.ts index 529f686..64b2711 100644 --- a/src/routes/dashboard/admin/ysws-review/+page.server.ts +++ b/src/routes/dashboard/admin/ysws-review/+page.server.ts @@ -9,7 +9,7 @@ export async function load({ locals }) { throw error(500); } if (!locals.user.hasT2Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const projects = await getProjects(['printed'], [], []); @@ -43,7 +43,7 @@ export const actions = { throw error(500); } if (!locals.user.hasT2Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const data = await request.formData(); 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 29f4e69..d27142f 100644 --- a/src/routes/dashboard/admin/ysws-review/[id]/+page.server.ts +++ b/src/routes/dashboard/admin/ysws-review/[id]/+page.server.ts @@ -15,7 +15,7 @@ export async function load({ locals, params }) { throw error(500); } if (!locals.user.hasT2Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); @@ -96,7 +96,7 @@ export const actions = { throw error(500); } if (!locals.user.hasT2Review) { - throw error(403, { message: 'get out, peasant' }); + throw error(403, { message: 'oi get out' }); } const id: number = parseInt(params.id); diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index 547481c..c6adbd0 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -12,7 +12,7 @@ {#if data.marketItems.length === 0} {:else} -asdasd +Market? {/if} From 179f959890b5be9b001880b7011de6b454477e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 07:49:35 +0100 Subject: [PATCH 04/25] Added snowflakes to page --- package-lock.json | 77 +++++++++++++++++++++++++-------------- package.json | 3 ++ src/routes/+layout.svelte | 42 +++++++++++++++++++++ 3 files changed, 95 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6c020c..3ba81e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,9 @@ "drizzle-orm": "^0.44.7", "express": "^5.1.0", "pg": "^8.16.3", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-snowfall": "^2.4.0", "sharp": "^0.34.4", "three": "^0.181.0", "tiny-relative-date": "^2.0.2", @@ -984,7 +987,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3028,7 +3030,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3050,7 +3051,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -3063,7 +3063,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3079,7 +3078,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -3467,7 +3465,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3484,7 +3481,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", @@ -3502,7 +3498,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=14" } @@ -5309,7 +5304,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.47.0.tgz", "integrity": "sha512-mznN01MBXtr4T7X/E3ENkhF6GzqxTxL6/whG3OzCzUu8G8KYRNiCdoxLMVWAHJx/mDMPP3XAeKCMZHF/Xd/CDw==", "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -5348,7 +5342,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -5855,7 +5848,6 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -6111,7 +6103,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6388,7 +6379,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -7002,7 +6992,6 @@ "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7099,7 +7088,6 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9399,7 +9387,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -9521,7 +9508,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9694,7 +9680,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9711,7 +9696,6 @@ "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -9918,6 +9902,46 @@ "url": "https://opencollective.com/express" } }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-snowfall": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-snowfall/-/react-snowfall-2.4.0.tgz", + "integrity": "sha512-KAPMiGnxt11PEgC2pTVrTQsvk5jt1kLUtG+ZamiKLphTZ7GiYT1Aa5kX6jp4jKWq1kqJHchnGT9CDm4g86A5Gg==", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.2.2" + }, + "peerDependencies": { + "react": "^16.8 || 17.x || 18.x || 19.x", + "react-dom": "^16.8 || 17.x || 18.x || 19.x" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -10017,7 +10041,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -10142,6 +10165,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -10595,7 +10624,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.40.1.tgz", "integrity": "sha512-0R3t2oiLxJNJb2buz61MNfPdkjeyj2qTCM7TtIv/4ZfF12zD7Ig8iIo+C8febroy+9S4QJ7qfijtearSdO/1ww==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -10688,8 +10716,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -10854,7 +10881,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10913,7 +10939,6 @@ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "license": "MIT", - "peer": true, "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", @@ -11064,7 +11089,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -11640,7 +11664,6 @@ "integrity": "sha512-HwaJmXO3M1r4S8x2ea2vy8Rw/y/38HRQuK/gNDRQ7w9cJXn6xSl1sIIqKCffULSUjul3wV3I3Nd/GfbmsRReEA==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "bin": { "workerd": "bin/workerd" }, diff --git a/package.json b/package.json index 7b7a994..35bf8e2 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,9 @@ "drizzle-orm": "^0.44.7", "express": "^5.1.0", "pg": "^8.16.3", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-snowfall": "^2.4.0", "sharp": "^0.34.4", "three": "^0.181.0", "tiny-relative-date": "^2.0.2", diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 2d98d65..f43d2c8 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,49 @@ + + {@render children?.()} \ No newline at end of file From 7e6ab1821dc7503767867fb5f9a5a7f75f2193f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 08:37:09 +0100 Subject: [PATCH 05/25] Add admin CRUD actions for market items --- .../admin/admin/market/+page.server.ts | 115 +++++++- .../dashboard/admin/admin/market/+page.svelte | 257 +++++++++++++++++- 2 files changed, 366 insertions(+), 6 deletions(-) diff --git a/src/routes/dashboard/admin/admin/market/+page.server.ts b/src/routes/dashboard/admin/admin/market/+page.server.ts index b941f82..358fe46 100644 --- a/src/routes/dashboard/admin/admin/market/+page.server.ts +++ b/src/routes/dashboard/admin/admin/market/+page.server.ts @@ -1,7 +1,8 @@ import { db } from '$lib/server/db/index.js'; import { marketItem } from '$lib/server/db/schema.js'; -import { error } from '@sveltejs/kit'; +import { error, fail } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; +import type { Actions } from './$types'; export async function load({ locals }) { if (!locals.user) { @@ -20,3 +21,115 @@ export async function load({ locals }) { marketItems }; } + +export const actions: Actions = { + create: async ({ request, locals }) => { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const formData = await request.formData(); + const name = formData.get('name')?.toString(); + const description = formData.get('description')?.toString(); + const image = formData.get('image')?.toString(); + const minRequiredShopScore = parseInt(formData.get('minRequiredShopScore')?.toString() || '0'); + const minShopScore = parseInt(formData.get('minShopScore')?.toString() || '0'); + const maxShopScore = parseInt(formData.get('maxShopScore')?.toString() || '0'); + const minPrice = parseInt(formData.get('minPrice')?.toString() || '0'); + const maxPrice = parseInt(formData.get('maxPrice')?.toString() || '0'); + const isPublic = formData.get('isPublic') === 'on'; + + if (!name || !description || !image) { + throw error(400, { message: 'Missing required fields' }); + } + + if (maxPrice < minPrice) { + throw error(400, { message: 'Max price must be greater than or equal to min price' }); + } + + if (maxShopScore < minShopScore) { + throw error(400, { message: 'Max shop score must be greater than or equal to min shop score' }); + } + + await db.insert(marketItem).values({ + createdBy: locals.user.id, + name, + description, + image, + minRequiredShopScore, + minShopScore, + maxShopScore, + minPrice, + maxPrice, + isPublic + }); + + return { success: true }; + }, + update: async ({ request, locals }) => { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const formData = await request.formData(); + const id = parseInt(formData.get('id')?.toString() || '0'); + const name = formData.get('name')?.toString(); + const description = formData.get('description')?.toString(); + const image = formData.get('image')?.toString(); + const minRequiredShopScore = parseInt(formData.get('minRequiredShopScore')?.toString() || '0'); + const minShopScore = parseInt(formData.get('minShopScore')?.toString() || '0'); + const maxShopScore = parseInt(formData.get('maxShopScore')?.toString() || '0'); + const minPrice = parseInt(formData.get('minPrice')?.toString() || '0'); + const maxPrice = parseInt(formData.get('maxPrice')?.toString() || '0'); + const isPublic = formData.get('isPublic') === 'on'; + + if (!id || !name || !description || !image) { + throw error(400, { message: 'Missing required fields' }); + } + + if (maxPrice < minPrice) { + throw error(400, { message: 'Max price must be greater than or equal to min price' }); + } + + if (maxShopScore < minShopScore) { + throw error(400, { message: 'Max shop score must be greater than or equal to min shop score' }); + } + + await db + .update(marketItem) + .set({ + name, + description, + image, + minRequiredShopScore, + minShopScore, + maxShopScore, + minPrice, + maxPrice, + isPublic, + updatedAt: new Date() + }) + .where(eq(marketItem.id, id)); + + return { success: true }; + }, + delete: async ({ request, locals }) => { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const formData = await request.formData(); + const id = parseInt(formData.get('id')?.toString() || '0'); + + if (!id) { + throw error(400, { message: 'Missing item id' }); + } + + await db + .update(marketItem) + .set({ deleted: true, updatedAt: new Date() }) + .where(eq(marketItem.id, id)); + + return { success: true }; + } +}; diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte index c1ec691..b30ff00 100644 --- a/src/routes/dashboard/admin/admin/market/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -1,15 +1,262 @@ -

Market

+

Market

-
- -
+ + +{#if showForm} +
+

+ {editingId ? 'Edit' : 'Create'} Item +

+
{ + return async ({ update }) => { + await update(); + closeForm(); + }; + }} + > + {#if editingId} + + {/if} + +
+ + +
+ +
+ + +
- +
+ + +
+ +
+ + +
+ +
+
Shop Score Range
+
+
+
+ + +
+ — +
+ + +
+
+
+
+
+
+
+
+ + +
+ +
+
Price Range
+
+
+
+ + +
+ — +
+ + +
+
+
+
+
+
+
+
+ + +
+ +
+ +
+ +
+ + +
+
+
+{/if} + +
+ {#each sortedItems as item (item.id)} +
+ {item.name} +

{item.name}

+

{item.description}

+

Price: {item.minPrice} - {item.maxPrice}

+

Score: {item.minShopScore} - {item.maxShopScore}

+ {#if item.isPublic} +

Public

+ {/if} + +
+ +
+ + +
+
+
+ {/each} +
From a4c6450cb928904e98310eef7217d976a6f2335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 08:57:43 +0100 Subject: [PATCH 06/25] Display market items in grid and order by max price --- src/routes/dashboard/market/+page.server.ts | 3 ++- src/routes/dashboard/market/+page.svelte | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/routes/dashboard/market/+page.server.ts b/src/routes/dashboard/market/+page.server.ts index 7d25d99..ff7f086 100644 --- a/src/routes/dashboard/market/+page.server.ts +++ b/src/routes/dashboard/market/+page.server.ts @@ -21,7 +21,8 @@ export async function load({ locals }) { eq(marketItem.deleted, false), locals.user.hasAdmin ? undefined : eq(marketItem.isPublic, true) ) - ); + ) + .orderBy(marketItem.maxPrice); return { marketItems diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index c6adbd0..c8f7cd6 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -12,7 +12,21 @@ {#if data.marketItems.length === 0} {:else} -Market? +
+ {#each data.marketItems as item (item.id)} +
+ {item.name} +
+

{item.name}

+

{item.description}

+
+
+ {/each} +
{/if} From c27c0d717b57efe7eb50c1789bd12eaccd0ecb5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 09:02:11 +0100 Subject: [PATCH 07/25] Separate public and draft market items in admin view --- .../dashboard/admin/admin/market/+page.svelte | 89 +++++++++++++------ 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte index b30ff00..437da3d 100644 --- a/src/routes/dashboard/admin/admin/market/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -4,13 +4,16 @@ let { data } = $props(); - let sortedItems = $derived( - [...data.marketItems].sort((a, b) => { - if (a.isPublic !== b.isPublic) { - return a.isPublic ? -1 : 1; - } - return a.minPrice - b.minPrice; - }) + let publicItems = $derived( + data.marketItems + .filter((item) => item.isPublic) + .sort((a, b) => a.minPrice - b.minPrice) + ); + + let draftItems = $derived( + data.marketItems + .filter((item) => !item.isPublic) + .sort((a, b) => a.minPrice - b.minPrice) ); let showForm = $state(false); @@ -238,25 +241,59 @@
{/if} -
- {#each sortedItems as item (item.id)} -
- {item.name} -

{item.name}

-

{item.description}

-

Price: {item.minPrice} - {item.maxPrice}

-

Score: {item.minShopScore} - {item.maxShopScore}

- {#if item.isPublic} -

Public

- {/if} +
+
+

Public

+ {#if publicItems.length === 0} +

No public items yet.

+ {:else} +
+ {#each publicItems as item (item.id)} +
+ {item.name} +

{item.name}

+

{item.description}

+

Price: {item.minPrice} - {item.maxPrice}

+

Score: {item.minShopScore} - {item.maxShopScore}

-
- -
- - -
+
+ +
+ + +
+
+
+ {/each} +
+ {/if} +
+ +
+

Drafts

+ {#if draftItems.length === 0} +

No drafts yet.

+ {:else} +
+ {#each draftItems as item (item.id)} +
+ {item.name} +

{item.name}

+

{item.description}

+

Price: {item.minPrice} - {item.maxPrice}

+

Score: {item.minShopScore} - {item.maxShopScore}

+

Draft (not public)

+ +
+ +
+ + +
+
+
+ {/each}
-
- {/each} + {/if} +
From eea22e122568972b1d152d8f4022de2e9816149c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 13:16:22 +0100 Subject: [PATCH 08/25] Enhance market item pricing and display logic --- .../dashboard/admin/admin/market/+page.svelte | 16 +++-- src/routes/dashboard/market/+page.server.ts | 38 +++++++++++- src/routes/dashboard/market/+page.svelte | 58 +++++++++++++++++-- 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte index 437da3d..12e3f1c 100644 --- a/src/routes/dashboard/admin/admin/market/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -24,7 +24,7 @@ image: '', minRequiredShopScore: '0', minShopScore: '0', - maxShopScore: '0', + maxShopScore: '10000', minPrice: '0', maxPrice: '0', isPublic: false @@ -54,7 +54,7 @@ image: '', minRequiredShopScore: '0', minShopScore: '0', - maxShopScore: '0', + maxShopScore: '10000', minPrice: '0', maxPrice: '0', isPublic: false @@ -160,7 +160,7 @@ id="maxShopScoreInput" type="number" min={formData.minShopScore} - max="1000" + max="10000" bind:value={formData.maxShopScore} class="themed-input w-full px-3 py-2 text-center" /> @@ -170,7 +170,7 @@
@@ -250,7 +250,9 @@
{#each publicItems as item (item.id)}
- {item.name} +
+ {item.name} +

{item.name}

{item.description}

Price: {item.minPrice} - {item.maxPrice}

@@ -277,7 +279,9 @@
{#each draftItems as item (item.id)}
- {item.name} +
+ {item.name} +

{item.name}

{item.description}

Price: {item.minPrice} - {item.maxPrice}

diff --git a/src/routes/dashboard/market/+page.server.ts b/src/routes/dashboard/market/+page.server.ts index ff7f086..6ebc5a1 100644 --- a/src/routes/dashboard/market/+page.server.ts +++ b/src/routes/dashboard/market/+page.server.ts @@ -13,7 +13,12 @@ export async function load({ locals }) { id: marketItem.id, name: marketItem.name, description: marketItem.description, - image: marketItem.image + image: marketItem.image, + minPrice: marketItem.minPrice, + maxPrice: marketItem.maxPrice, + minShopScore: marketItem.minShopScore, + maxShopScore: marketItem.maxShopScore, + minRequiredShopScore: marketItem.minRequiredShopScore }) .from(marketItem) .where( @@ -24,7 +29,36 @@ export async function load({ locals }) { ) .orderBy(marketItem.maxPrice); + const shopScore = Number(locals.user?.shopScore || 0); + const marketItemsWithPrice = marketItems + .map((item) => { + const max = Number(item.maxPrice || 0); + const min = Number(item.minPrice || 0); + const diff = Math.max(0, max - min); + + const minShop = Number(item.minShopScore || 0); + const maxShop = Number(item.maxShopScore || 0); + let discountPercent = 0; + if (maxShop > minShop) { + discountPercent = (shopScore - minShop) / (maxShop - minShop); + discountPercent = Math.max(0, Math.min(1, discountPercent)); + } else { + discountPercent = 0; + } + + const discountAmount = diff * discountPercent; + const rawPrice = Math.ceil(max - discountAmount); + const computedPrice = Math.max(rawPrice, min); + return { ...item, computedPrice }; + }) + .filter((item) => { + if (locals.user?.hasAdmin) return true; + const minReq = Number(item.minRequiredShopScore || 0); + return shopScore >= minReq; + }) + .sort((a, b) => a.computedPrice - b.computedPrice); + return { - marketItems + marketItems: marketItemsWithPrice }; } diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index c8f7cd6..404fec1 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -3,6 +3,44 @@ import MarketTimer from './MarketTimer.svelte'; let { data } = $props(); + + function computeUserPrice(item: any) { + if (typeof item.computedPrice === 'number') return item.computedPrice; + + const max = Number(item.maxPrice || 0); + const min = Number(item.minPrice || 0); + const diff = Math.max(0, max - min); + const shopScore = Number(data?.user?.shopScore || 0); + + const minShop = Number(item.minShopScore || 0); + const maxShop = Number(item.maxShopScore || 0); + let discountPercent = 0; + if (maxShop > minShop) { + discountPercent = (shopScore - minShop) / (maxShop - minShop); + discountPercent = Math.max(0, Math.min(1, discountPercent)); + } else { + discountPercent = 0; + } + + const discountAmount = diff * discountPercent; + const rawPrice = Math.ceil(max - discountAmount); + const price = Math.max(rawPrice, min); + return price; + } + + function getPriceInfo(item: any) { + const max = Number(item.maxPrice || 0); + const price = computeUserPrice(item); + const save = Math.max(0, max - price); + const hasDiscount = save > 0 && max > 0; + const percentOff = hasDiscount ? Math.round((save / max) * 100) : 0; + return { price, save, hasDiscount, percentOff }; + } + + function formatBricks(value: number | string) { + const n = Number(value || 0); + return `${n.toLocaleString()} bricks`; + } @@ -15,14 +53,24 @@
{#each data.marketItems as item (item.id)}
- {item.name} +
+ {item.name} +

{item.name}

{item.description}

+ {#if getPriceInfo(item).hasDiscount} +
+
{formatBricks(item.maxPrice)}
+
{formatBricks(getPriceInfo(item).price)}
+
+
+
{getPriceInfo(item).percentOff}% off
+
You save {formatBricks(getPriceInfo(item).save)}
+
+ {:else} +
{formatBricks(getPriceInfo(item).price)}
+ {/if}
{/each} From 3b340ce9249ef535a7cc7755a926ec579d61af2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 13:45:32 +0100 Subject: [PATCH 09/25] Round discount up --- src/routes/dashboard/market/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index 404fec1..04e35ce 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -33,7 +33,7 @@ const price = computeUserPrice(item); const save = Math.max(0, max - price); const hasDiscount = save > 0 && max > 0; - const percentOff = hasDiscount ? Math.round((save / max) * 100) : 0; + const percentOff = hasDiscount ? Math.ceil((save / max) * 100) : 0; return { price, save, hasDiscount, percentOff }; } From 3ee6be795d016e82e66182d40f0d48121a5b6c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 14:08:17 +0100 Subject: [PATCH 10/25] Bricks to clays --- src/routes/dashboard/market/+page.svelte | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index 04e35ce..b8ad586 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -37,9 +37,9 @@ return { price, save, hasDiscount, percentOff }; } - function formatBricks(value: number | string) { + function formatClays(value: number | string) { const n = Number(value || 0); - return `${n.toLocaleString()} bricks`; + return `${n.toLocaleString()} clays`; } @@ -61,15 +61,15 @@

{item.description}

{#if getPriceInfo(item).hasDiscount}
-
{formatBricks(item.maxPrice)}
-
{formatBricks(getPriceInfo(item).price)}
+
{formatClays(item.maxPrice)}
+
{formatClays(getPriceInfo(item).price)}
{getPriceInfo(item).percentOff}% off
-
You save {formatBricks(getPriceInfo(item).save)}
+
You save {formatClays(getPriceInfo(item).save)}
{:else} -
{formatBricks(getPriceInfo(item).price)}
+
{formatClays(getPriceInfo(item).price)}
{/if}
From efcab5f78b763790d7dbbae7f52655ea3d041a2f Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 14:31:44 +0000 Subject: [PATCH 11/25] Update hackatime page text --- src/routes/auth/create-hackatime-account/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/auth/create-hackatime-account/+page.svelte b/src/routes/auth/create-hackatime-account/+page.svelte index e722062..62d3c53 100644 --- a/src/routes/auth/create-hackatime-account/+page.svelte +++ b/src/routes/auth/create-hackatime-account/+page.svelte @@ -7,7 +7,7 @@

Hackatime account not found

-

Make sure you have a Hackatime account associated with your Hack Club Auth or Slack account.

+

Make sure you have a Hackatime account associated with your Slack account.

Make one? Try again
\ No newline at end of file From 5499e2967f21bef6426006becafbd677b9d6d773 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz <40526225+ArcaEge@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:33:52 +0300 Subject: [PATCH 12/25] Revert "Snowfall add to page" --- package-lock.json | 77 ++++++++++++++------------------------- package.json | 3 -- src/routes/+layout.svelte | 42 --------------------- 3 files changed, 27 insertions(+), 95 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3ba81e9..f6c020c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,6 @@ "drizzle-orm": "^0.44.7", "express": "^5.1.0", "pg": "^8.16.3", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-snowfall": "^2.4.0", "sharp": "^0.34.4", "three": "^0.181.0", "tiny-relative-date": "^2.0.2", @@ -987,6 +984,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3030,6 +3028,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -3051,6 +3050,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -3063,6 +3063,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3078,6 +3079,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz", "integrity": "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", @@ -3465,6 +3467,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3481,6 +3484,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", @@ -3498,6 +3502,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -5304,6 +5309,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.47.0.tgz", "integrity": "sha512-mznN01MBXtr4T7X/E3ENkhF6GzqxTxL6/whG3OzCzUu8G8KYRNiCdoxLMVWAHJx/mDMPP3XAeKCMZHF/Xd/CDw==", "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -5342,6 +5348,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -5848,6 +5855,7 @@ "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.1", "@typescript-eslint/types": "8.46.1", @@ -6103,6 +6111,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6379,6 +6388,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -6992,6 +7002,7 @@ "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7088,6 +7099,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9387,6 +9399,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -9508,6 +9521,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -9680,6 +9694,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9696,6 +9711,7 @@ "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -9902,46 +9918,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.3" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" - }, - "node_modules/react-snowfall": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-snowfall/-/react-snowfall-2.4.0.tgz", - "integrity": "sha512-KAPMiGnxt11PEgC2pTVrTQsvk5jt1kLUtG+ZamiKLphTZ7GiYT1Aa5kX6jp4jKWq1kqJHchnGT9CDm4g86A5Gg==", - "license": "MIT", - "dependencies": { - "react-fast-compare": "^3.2.2" - }, - "peerDependencies": { - "react": "^16.8 || 17.x || 18.x || 19.x", - "react-dom": "^16.8 || 17.x || 18.x || 19.x" - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -10041,6 +10017,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -10165,12 +10142,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -10624,6 +10595,7 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.40.1.tgz", "integrity": "sha512-0R3t2oiLxJNJb2buz61MNfPdkjeyj2qTCM7TtIv/4ZfF12zD7Ig8iIo+C8febroy+9S4QJ7qfijtearSdO/1ww==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -10716,7 +10688,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", "integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -10881,6 +10854,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10939,6 +10913,7 @@ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "license": "MIT", + "peer": true, "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", @@ -11089,6 +11064,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -11664,6 +11640,7 @@ "integrity": "sha512-HwaJmXO3M1r4S8x2ea2vy8Rw/y/38HRQuK/gNDRQ7w9cJXn6xSl1sIIqKCffULSUjul3wV3I3Nd/GfbmsRReEA==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "bin": { "workerd": "bin/workerd" }, diff --git a/package.json b/package.json index 35bf8e2..7b7a994 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,6 @@ "drizzle-orm": "^0.44.7", "express": "^5.1.0", "pg": "^8.16.3", - "react": "^19.2.3", - "react-dom": "^19.2.3", - "react-snowfall": "^2.4.0", "sharp": "^0.34.4", "three": "^0.181.0", "tiny-relative-date": "^2.0.2", diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f43d2c8..2d98d65 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,49 +1,7 @@ - - {@render children?.()} \ No newline at end of file From ea975b8b5b9e9dcf7e3aa997a1c6bd73a5078bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Tue, 23 Dec 2025 15:36:11 +0100 Subject: [PATCH 13/25] Add infinite scroll to explore page --- src/routes/dashboard/explore/+page.server.ts | 40 ++------- src/routes/dashboard/explore/+page.svelte | 91 +++++++++++++++++++- src/routes/dashboard/explore/+server.ts | 20 +++++ src/routes/dashboard/explore/devlogs.ts | 34 ++++++++ 4 files changed, 151 insertions(+), 34 deletions(-) create mode 100644 src/routes/dashboard/explore/+server.ts create mode 100644 src/routes/dashboard/explore/devlogs.ts diff --git a/src/routes/dashboard/explore/+page.server.ts b/src/routes/dashboard/explore/+page.server.ts index 68871eb..3d2d4de 100644 --- a/src/routes/dashboard/explore/+page.server.ts +++ b/src/routes/dashboard/explore/+page.server.ts @@ -1,36 +1,12 @@ -import { db } from '$lib/server/db/index.js'; -import { devlog, project, user } from '$lib/server/db/schema.js'; -import { eq, desc } from 'drizzle-orm'; +import { DEVLOGS_PAGE_SIZE, fetchExploreDevlogs } from './devlogs.js'; -// TODO: pagination export async function load() { - const devlogs = await db - .select({ - devlog: { - id: devlog.id, - description: devlog.description, - image: devlog.image, - model: devlog.model, - timeSpent: devlog.timeSpent, - createdAt: devlog.createdAt - }, - project: { - id: project.id, - name: project.name - }, - user: { - id: user.id, - name: user.name - } - }) - .from(devlog) - .innerJoin(project, eq(devlog.projectId, project.id)) - .innerJoin(user, eq(devlog.userId, user.id)) - .where(eq(devlog.deleted, false)) - .orderBy(desc(devlog.createdAt)) - .limit(15); + const devlogs = await fetchExploreDevlogs(0); + const nextOffset = devlogs.length; - return { - devlogs - }; + return { + devlogs, + nextOffset, + hasMore: devlogs.length === DEVLOGS_PAGE_SIZE + }; } diff --git a/src/routes/dashboard/explore/+page.svelte b/src/routes/dashboard/explore/+page.svelte index a56b5ad..2ac2c3f 100644 --- a/src/routes/dashboard/explore/+page.svelte +++ b/src/routes/dashboard/explore/+page.svelte @@ -1,16 +1,84 @@

Explore

+
- {#if data.devlogs.length == 0} + {#if devlogs.length == 0}
No journal entries yet
{:else} - {#each data.devlogs as devlog} + {#each devlogs as devlog (devlog.devlog.id)} {/each} + +
+ + {#if loadingMore} +

Loading more...

+ {/if} + + {#if loadError} +
+ {loadError} + +
+ {/if} + + {#if !hasMore && !loadingMore} +

You're caught up.

+ {/if} {/if}
\ No newline at end of file diff --git a/src/routes/dashboard/explore/+server.ts b/src/routes/dashboard/explore/+server.ts new file mode 100644 index 0000000..5bb2f19 --- /dev/null +++ b/src/routes/dashboard/explore/+server.ts @@ -0,0 +1,20 @@ +import { json } from '@sveltejs/kit'; +import { DEVLOGS_PAGE_SIZE, fetchExploreDevlogs } from './devlogs.js'; + +export async function GET({ url }) { + const offsetParam = url.searchParams.get('offset'); + const offset = offsetParam ? Number.parseInt(offsetParam, 10) : 0; + + if (!Number.isFinite(offset) || offset < 0) { + return json({ error: 'Invalid offset' }, { status: 400 }); + } + + const devlogs = await fetchExploreDevlogs(offset); + const nextOffset = offset + devlogs.length; + + return json({ + devlogs, + nextOffset, + hasMore: devlogs.length === DEVLOGS_PAGE_SIZE + }); +} diff --git a/src/routes/dashboard/explore/devlogs.ts b/src/routes/dashboard/explore/devlogs.ts new file mode 100644 index 0000000..595fd32 --- /dev/null +++ b/src/routes/dashboard/explore/devlogs.ts @@ -0,0 +1,34 @@ +import { db } from '$lib/server/db/index.js'; +import { devlog, project, user } from '$lib/server/db/schema.js'; +import { desc, eq } from 'drizzle-orm'; + +export const DEVLOGS_PAGE_SIZE = 15; + +export async function fetchExploreDevlogs(offset: number, limit = DEVLOGS_PAGE_SIZE) { + return db + .select({ + devlog: { + id: devlog.id, + description: devlog.description, + image: devlog.image, + model: devlog.model, + timeSpent: devlog.timeSpent, + createdAt: devlog.createdAt + }, + project: { + id: project.id, + name: project.name + }, + user: { + id: user.id, + name: user.name + } + }) + .from(devlog) + .innerJoin(project, eq(devlog.projectId, project.id)) + .innerJoin(user, eq(devlog.userId, user.id)) + .where(eq(devlog.deleted, false)) + .orderBy(desc(devlog.createdAt), desc(devlog.id)) + .offset(offset) + .limit(limit); +} From de8817f299b5497b3f6ce55bc0dcc5aaa038fded Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 16:19:01 +0000 Subject: [PATCH 14/25] Refactor create shop item code, separate out create page --- .../admin/admin/market/+page.server.ts | 7 +- .../dashboard/admin/admin/market/+page.svelte | 269 +----------------- .../admin/admin/market/MarketItem.svelte | 20 ++ .../admin/admin/market/create/+page.server.ts | 62 ++++ .../admin/admin/market/create/+page.svelte | 127 +++++++++ 5 files changed, 226 insertions(+), 259 deletions(-) create mode 100644 src/routes/dashboard/admin/admin/market/MarketItem.svelte create mode 100644 src/routes/dashboard/admin/admin/market/create/+page.server.ts create mode 100644 src/routes/dashboard/admin/admin/market/create/+page.svelte diff --git a/src/routes/dashboard/admin/admin/market/+page.server.ts b/src/routes/dashboard/admin/admin/market/+page.server.ts index 358fe46..db5fe47 100644 --- a/src/routes/dashboard/admin/admin/market/+page.server.ts +++ b/src/routes/dashboard/admin/admin/market/+page.server.ts @@ -1,14 +1,11 @@ import { db } from '$lib/server/db/index.js'; import { marketItem } from '$lib/server/db/schema.js'; -import { error, fail } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit'; import { eq } from 'drizzle-orm'; import type { Actions } from './$types'; export async function load({ locals }) { - if (!locals.user) { - throw error(500); - } - if (!locals.user.hasAdmin) { + if (!locals.user?.hasAdmin) { throw error(403, { message: 'oi get out' }); } diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte index 12e3f1c..55e5705 100644 --- a/src/routes/dashboard/admin/admin/market/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -1,22 +1,18 @@ -

Market

- - - -{#if showForm} -
-

- {editingId ? 'Edit' : 'Create'} Item -

-
{ - return async ({ update }) => { - await update(); - closeForm(); - }; - }} - > - {#if editingId} - - {/if} - -
- - -
- -
- - -
+

Market

-
- - -
- -
- - -
- -
-
Shop Score Range
-
-
-
- - -
- — -
- - -
-
-
-
-
-
-
-
- - -
- -
-
Price Range
-
-
-
- - -
- — -
- - -
-
-
-
-
-
-
-
- - -
- -
- -
- -
- - -
-
-
-{/if} +
-

Public

+

Public

{#if publicItems.length === 0}

No public items yet.

{:else}
{#each publicItems as item (item.id)} -
-
- {item.name} -
-

{item.name}

-

{item.description}

-

Price: {item.minPrice} - {item.maxPrice}

-

Score: {item.minShopScore} - {item.maxShopScore}

- -
- -
- - -
-
-
+ {/each}
{/if}
-

Drafts

- {#if draftItems.length === 0} -

No drafts yet.

+

Private

+ {#if privateItems.length === 0} +

Nothing to see here.

{:else}
- {#each draftItems as item (item.id)} -
-
- {item.name} -
-

{item.name}

-

{item.description}

-

Price: {item.minPrice} - {item.maxPrice}

-

Score: {item.minShopScore} - {item.maxShopScore}

-

Draft (not public)

- -
- -
- - -
-
-
+ {#each privateItems as item (item.id)} + {/each}
{/if} diff --git a/src/routes/dashboard/admin/admin/market/MarketItem.svelte b/src/routes/dashboard/admin/admin/market/MarketItem.svelte new file mode 100644 index 0000000..5d6844c --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/MarketItem.svelte @@ -0,0 +1,20 @@ + + +
+
+ {item.name} +
+

{item.name}

+

{item.description}

+

Price: {item.maxPrice}-{item.minPrice}

+

+ Market score: {item.minRequiredShopScore}, {item.minShopScore}-{item.maxShopScore} +

+ +
+ Edit + Delete +
+
diff --git a/src/routes/dashboard/admin/admin/market/create/+page.server.ts b/src/routes/dashboard/admin/admin/market/create/+page.server.ts new file mode 100644 index 0000000..378c949 --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/create/+page.server.ts @@ -0,0 +1,62 @@ +import { db } from '$lib/server/db/index.js'; +import { marketItem } from '$lib/server/db/schema.js'; +import { error } from '@sveltejs/kit'; +import type { Actions } from './$types'; + +export async function load({ locals }) { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + return {}; +} + +export const actions: Actions = { + default: async ({ request, locals }) => { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const formData = await request.formData(); + const name = formData.get('name')?.toString(); + const description = formData.get('description')?.toString(); + const image = formData.get('image')?.toString(); + const minRequiredShopScore = parseInt(formData.get('minRequiredShopScore')?.toString() || '0'); + const minShopScore = parseInt(formData.get('minShopScore')?.toString() || '0'); + const maxShopScore = parseInt(formData.get('maxShopScore')?.toString() || '0'); + const minPrice = parseInt(formData.get('minPrice')?.toString() || '0'); + const maxPrice = parseInt(formData.get('maxPrice')?.toString() || '0'); + const isPublic = formData.get('isPublic') === 'on'; + + // Don't need to implement proper validation, page is admins only + + if (!name || !description || !image) { + throw error(400, { message: 'Missing required fields' }); + } + + if (maxPrice < minPrice) { + throw error(400, { message: 'Max price must be greater than or equal to min price' }); + } + + if (maxShopScore < minShopScore) { + throw error(400, { + message: 'Max shop score must be greater than or equal to min shop score' + }); + } + + await db.insert(marketItem).values({ + createdBy: locals.user.id, + name, + description, + image, + minRequiredShopScore, + minShopScore, + maxShopScore, + minPrice, + maxPrice, + isPublic + }); + + return { success: true }; + } +}; diff --git a/src/routes/dashboard/admin/admin/market/create/+page.svelte b/src/routes/dashboard/admin/admin/market/create/+page.svelte new file mode 100644 index 0000000..63f49aa --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/create/+page.svelte @@ -0,0 +1,127 @@ + + + + +

Add market item

+ +
{ + return async ({ update }) => { + await update(); + }; + }} +> + + + + + + + + +
+

Pricing

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + + +
+ + Cancel +
+
From dde49beefcdb1e904bc0cf21c42ce0135101734d Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 17:00:58 +0000 Subject: [PATCH 15/25] Change edit page --- .../admin/admin/market/+page.server.ts | 52 +------ .../admin/market/[id]/edit/+page.server.ts | 89 +++++++++++ .../admin/admin/market/[id]/edit/+page.svelte | 145 ++++++++++++++++++ .../admin/admin/market/create/+page.server.ts | 4 +- .../admin/admin/market/create/+page.svelte | 18 ++- 5 files changed, 255 insertions(+), 53 deletions(-) create mode 100644 src/routes/dashboard/admin/admin/market/[id]/edit/+page.server.ts create mode 100644 src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte diff --git a/src/routes/dashboard/admin/admin/market/+page.server.ts b/src/routes/dashboard/admin/admin/market/+page.server.ts index db5fe47..2c856bd 100644 --- a/src/routes/dashboard/admin/admin/market/+page.server.ts +++ b/src/routes/dashboard/admin/admin/market/+page.server.ts @@ -9,10 +9,7 @@ export async function load({ locals }) { throw error(403, { message: 'oi get out' }); } - const marketItems = await db - .select() - .from(marketItem) - .where(eq(marketItem.deleted, false)); + const marketItems = await db.select().from(marketItem).where(eq(marketItem.deleted, false)); return { marketItems @@ -20,49 +17,6 @@ export async function load({ locals }) { } export const actions: Actions = { - create: async ({ request, locals }) => { - if (!locals.user?.hasAdmin) { - throw error(403, { message: 'oi get out' }); - } - - const formData = await request.formData(); - const name = formData.get('name')?.toString(); - const description = formData.get('description')?.toString(); - const image = formData.get('image')?.toString(); - const minRequiredShopScore = parseInt(formData.get('minRequiredShopScore')?.toString() || '0'); - const minShopScore = parseInt(formData.get('minShopScore')?.toString() || '0'); - const maxShopScore = parseInt(formData.get('maxShopScore')?.toString() || '0'); - const minPrice = parseInt(formData.get('minPrice')?.toString() || '0'); - const maxPrice = parseInt(formData.get('maxPrice')?.toString() || '0'); - const isPublic = formData.get('isPublic') === 'on'; - - if (!name || !description || !image) { - throw error(400, { message: 'Missing required fields' }); - } - - if (maxPrice < minPrice) { - throw error(400, { message: 'Max price must be greater than or equal to min price' }); - } - - if (maxShopScore < minShopScore) { - throw error(400, { message: 'Max shop score must be greater than or equal to min shop score' }); - } - - await db.insert(marketItem).values({ - createdBy: locals.user.id, - name, - description, - image, - minRequiredShopScore, - minShopScore, - maxShopScore, - minPrice, - maxPrice, - isPublic - }); - - return { success: true }; - }, update: async ({ request, locals }) => { if (!locals.user?.hasAdmin) { throw error(403, { message: 'oi get out' }); @@ -89,7 +43,9 @@ export const actions: Actions = { } if (maxShopScore < minShopScore) { - throw error(400, { message: 'Max shop score must be greater than or equal to min shop score' }); + throw error(400, { + message: 'Max shop score must be greater than or equal to min shop score' + }); } await db diff --git a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.server.ts b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.server.ts new file mode 100644 index 0000000..8a1deb9 --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.server.ts @@ -0,0 +1,89 @@ +import { db } from '$lib/server/db/index.js'; +import { marketItem } from '$lib/server/db/schema.js'; +import { error, redirect } from '@sveltejs/kit'; +import type { Actions } from './$types'; +import { eq, and } from 'drizzle-orm'; + +export async function load({ locals, params }) { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const id: number = parseInt(params.id); + + const [item] = await db + .select() + .from(marketItem) + .where(and(eq(marketItem.deleted, false), eq(marketItem.id, id))); + + if (!item) { + return error(404, { message: 'item not found' }); + } + + return { + marketItem: item + }; +} + +export const actions: Actions = { + default: async ({ request, locals, params }) => { + if (!locals.user?.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const id: number = parseInt(params.id); + + const [item] = await db + .select() + .from(marketItem) + .where(and(eq(marketItem.deleted, false), eq(marketItem.id, id))); + + if (!item) { + return error(404, { message: 'item not found' }); + } + + const formData = await request.formData(); + const name = formData.get('name')?.toString(); + const description = formData.get('description')?.toString(); + const image = formData.get('image')?.toString(); + const minRequiredShopScore = parseInt(formData.get('minRequiredShopScore')?.toString() || '0'); + const minShopScore = parseInt(formData.get('minShopScore')?.toString() || '0'); + const maxShopScore = parseInt(formData.get('maxShopScore')?.toString() || '0'); + const minPrice = parseInt(formData.get('minPrice')?.toString() || '0'); + const maxPrice = parseInt(formData.get('maxPrice')?.toString() || '0'); + const isPublic = formData.get('isPublic') === 'on'; + + // Don't really need to implement proper validation, page is admins only + + if (!name || !description || !image) { + throw error(400, { message: 'Missing required fields' }); + } + + if (maxPrice < minPrice) { + throw error(400, { message: 'Max price must be greater than or equal to min price' }); + } + + if (maxShopScore < minShopScore) { + throw error(400, { + message: 'Max shop score must be greater than or equal to min shop score' + }); + } + + await db + .update(marketItem) + .set({ + name, + description, + image, + minRequiredShopScore, + minShopScore, + maxShopScore, + minPrice, + maxPrice, + isPublic + }) + .where(eq(marketItem.id, id)); + + return redirect(302, '/dashboard/admin/admin/market'); + } +}; diff --git a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte new file mode 100644 index 0000000..245afb1 --- /dev/null +++ b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte @@ -0,0 +1,145 @@ + + + + +

Edit market item

+ +
{ + formPending = true; + return async ({ update }) => { + await update(); + formPending = false; + }; + }} +> + + + + + + + + +
+

Pricing

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + + +
+ + Cancel +
+
diff --git a/src/routes/dashboard/admin/admin/market/create/+page.server.ts b/src/routes/dashboard/admin/admin/market/create/+page.server.ts index 378c949..23b2462 100644 --- a/src/routes/dashboard/admin/admin/market/create/+page.server.ts +++ b/src/routes/dashboard/admin/admin/market/create/+page.server.ts @@ -1,6 +1,6 @@ import { db } from '$lib/server/db/index.js'; import { marketItem } from '$lib/server/db/schema.js'; -import { error } from '@sveltejs/kit'; +import { error, redirect } from '@sveltejs/kit'; import type { Actions } from './$types'; export async function load({ locals }) { @@ -57,6 +57,6 @@ export const actions: Actions = { isPublic }); - return { success: true }; + return redirect(302, '/dashboard/admin/admin/market'); } }; diff --git a/src/routes/dashboard/admin/admin/market/create/+page.svelte b/src/routes/dashboard/admin/admin/market/create/+page.svelte index 63f49aa..63d7543 100644 --- a/src/routes/dashboard/admin/admin/market/create/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/create/+page.svelte @@ -1,13 +1,14 @@ @@ -18,8 +19,10 @@ method="POST" class="flex flex-col gap-3" use:enhance={() => { + formPending = true; return async ({ update }) => { await update(); + formPending = false; }; }} > @@ -54,6 +57,7 @@ @@ -68,6 +72,8 @@ Min score Max score Max price (brick) @@ -104,7 +114,9 @@ Min price (brick)
- + Cancel
From 17517b98629f80e3f47f27f9245971d2ed48c143 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 18:40:23 +0000 Subject: [PATCH 16/25] Make edit page persist isPublic --- src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte index 245afb1..1a0ee24 100644 --- a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte @@ -134,7 +134,7 @@
From 450e34d310046c654afb085b269f0ac9a3f84d93 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 18:53:18 +0000 Subject: [PATCH 17/25] Add edit page previews on admin market --- .../dashboard/admin/admin/market/+page.svelte | 16 ++++++++-------- .../admin/admin/market/MarketItem.svelte | 14 ++++++++------ .../admin/admin/market/[id]/edit/+page.svelte | 7 ++++++- .../admin/admin/market/create/+page.svelte | 2 +- src/routes/dashboard/market/+page.svelte | 4 ++-- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte index 55e5705..147114d 100644 --- a/src/routes/dashboard/admin/admin/market/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -32,12 +32,12 @@

Market

-
-
-

Public

+
+
+

Public

{#if publicItems.length === 0}

No public items yet.

{:else} @@ -47,10 +47,10 @@ {/each}
{/if} -
+
-
-

Private

+
+

Private

{#if privateItems.length === 0}

Nothing to see here.

{:else} @@ -60,5 +60,5 @@ {/each}
{/if} -
+
diff --git a/src/routes/dashboard/admin/admin/market/MarketItem.svelte b/src/routes/dashboard/admin/admin/market/MarketItem.svelte index 5d6844c..b67e0a1 100644 --- a/src/routes/dashboard/admin/admin/market/MarketItem.svelte +++ b/src/routes/dashboard/admin/admin/market/MarketItem.svelte @@ -1,5 +1,5 @@
@@ -9,12 +9,14 @@

{item.name}

{item.description}

Price: {item.maxPrice}-{item.minPrice}

-

+

Market score: {item.minRequiredShopScore}, {item.minShopScore}-{item.maxShopScore}

-
- Edit - Delete -
+ {#if showButtons} +
+ Edit + Delete +
+ {/if}
diff --git a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte index 1a0ee24..d2755f1 100644 --- a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte @@ -1,6 +1,7 @@ + + + +

Delete market item

+ +
+ +
+ +
{ + formPending = true; + return async ({ update }) => { + await update(); + formPending = false; + }; + }} +> +

Are you sure you want to delete this? If possible, make it private instead.

+
+ Cancel + +
+
diff --git a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte index d2755f1..b31d334 100644 --- a/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/[id]/edit/+page.svelte @@ -145,6 +145,6 @@
- Cancel + Cancel
From a7d3681da8500b6bc41c070ee1c90688939dba9f Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 21:48:32 +0000 Subject: [PATCH 19/25] Clean up market page --- src/lib/components/MarketItem.svelte | 16 ----- src/lib/utils.ts | 23 ++++++ src/routes/dashboard/market/+page.server.ts | 42 ++++------- src/routes/dashboard/market/+page.svelte | 72 +++---------------- src/routes/dashboard/market/MarketItem.svelte | 42 +++++++++++ 5 files changed, 87 insertions(+), 108 deletions(-) delete mode 100644 src/lib/components/MarketItem.svelte create mode 100644 src/routes/dashboard/market/MarketItem.svelte diff --git a/src/lib/components/MarketItem.svelte b/src/lib/components/MarketItem.svelte deleted file mode 100644 index e1f0283..0000000 --- a/src/lib/components/MarketItem.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - -
- {#if url} - - {/if} -
- -
-
diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 1677fae..d3d0eef 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -55,3 +55,26 @@ export default function fileSizeFromUrl(url: string): Promise { export function formatMinutes(mins: number | null) { return Math.floor((mins ?? 0) / 60) + 'h ' + Math.floor((mins ?? 0) % 60) + 'min'; } + +export function calculateMarketPrice( + minPrice: number, + maxPrice: number, + minShopScore: number, + maxShopScore: number, + userShopScore: number +) { + if (userShopScore <= minShopScore) { + return maxPrice; + } else if (userShopScore >= maxShopScore) { + return minPrice; + } else { + const priceDiff = maxPrice - minPrice; + const shopScoreDiff = maxShopScore - minShopScore; + const m = priceDiff / shopScoreDiff; // diff_y/diff_x + + const shopScoreRemainder = userShopScore - minShopScore; + + // y = -mx + c + return Math.round(-m * shopScoreRemainder + maxPrice); + } +} diff --git a/src/routes/dashboard/market/+page.server.ts b/src/routes/dashboard/market/+page.server.ts index 6ebc5a1..8ab074e 100644 --- a/src/routes/dashboard/market/+page.server.ts +++ b/src/routes/dashboard/market/+page.server.ts @@ -1,5 +1,6 @@ import { db } from '$lib/server/db/index.js'; import { marketItem } from '$lib/server/db/schema.js'; +import { calculateMarketPrice } from '$lib/utils'; import { error } from '@sveltejs/kit'; import { eq, and } from 'drizzle-orm'; @@ -21,40 +22,23 @@ export async function load({ locals }) { minRequiredShopScore: marketItem.minRequiredShopScore }) .from(marketItem) - .where( - and( - eq(marketItem.deleted, false), - locals.user.hasAdmin ? undefined : eq(marketItem.isPublic, true) - ) - ) + .where(and(eq(marketItem.deleted, false), eq(marketItem.isPublic, true))) .orderBy(marketItem.maxPrice); - const shopScore = Number(locals.user?.shopScore || 0); + const shopScore = locals.user.shopScore; + const marketItemsWithPrice = marketItems .map((item) => { - const max = Number(item.maxPrice || 0); - const min = Number(item.minPrice || 0); - const diff = Math.max(0, max - min); - - const minShop = Number(item.minShopScore || 0); - const maxShop = Number(item.maxShopScore || 0); - let discountPercent = 0; - if (maxShop > minShop) { - discountPercent = (shopScore - minShop) / (maxShop - minShop); - discountPercent = Math.max(0, Math.min(1, discountPercent)); - } else { - discountPercent = 0; - } + const computedPrice = calculateMarketPrice( + item.minPrice, + item.maxPrice, + item.minShopScore, + item.maxShopScore, + shopScore + ); - const discountAmount = diff * discountPercent; - const rawPrice = Math.ceil(max - discountAmount); - const computedPrice = Math.max(rawPrice, min); - return { ...item, computedPrice }; - }) - .filter((item) => { - if (locals.user?.hasAdmin) return true; - const minReq = Number(item.minRequiredShopScore || 0); - return shopScore >= minReq; + const discountAmount = 1 - computedPrice / item.maxPrice; + return { ...item, computedPrice, discountAmount }; }) .sort((a, b) => a.computedPrice - b.computedPrice); diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index ba3487f..e3e2dd0 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -1,80 +1,26 @@ -

Market

+

Market

{#if data.marketItems.length === 0} {:else} -
+

+ Market score: {data.user.shopScore} + (allows you to get stuff for cheaper and unlock more items!) +

+ +
{#each data.marketItems as item (item.id)} -
-
- {item.name} -
-
-

{item.name}

-

{item.description}

- {#if getPriceInfo(item).hasDiscount} -
-
{formatClays(item.maxPrice)}
-
{formatClays(getPriceInfo(item).price)}
-
-
-
{getPriceInfo(item).percentOff}% off
-
You save {formatClays(getPriceInfo(item).save)}
-
- {:else} -
{formatClays(getPriceInfo(item).price)}
- {/if} -
-
+ {/each}
{/if} - - diff --git a/src/routes/dashboard/market/MarketItem.svelte b/src/routes/dashboard/market/MarketItem.svelte new file mode 100644 index 0000000..8572439 --- /dev/null +++ b/src/routes/dashboard/market/MarketItem.svelte @@ -0,0 +1,42 @@ + + + + +
+
+ {item.name} +
+
+

{item.name}

+

{item.description}

+ {#if item.discountAmount > 0} +
+
{item.maxPrice} bricks
+
+ {item.computedPrice} bricks +
+
+
+
+ {Math.round(item.discountAmount * 100)}% off +
+
You save {item.maxPrice - item.computedPrice} bricks
+
+ {:else} +
{item.computedPrice} bricks
+ {/if} +
+
From f1344482fed08a62623f3e5018cefc07d3e75d46 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 22:03:04 +0000 Subject: [PATCH 20/25] Add buy button to market items (currently going nowhere) --- src/routes/dashboard/market/+page.svelte | 2 +- src/routes/dashboard/market/MarketItem.svelte | 48 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/routes/dashboard/market/+page.svelte b/src/routes/dashboard/market/+page.svelte index e3e2dd0..6fcfd94 100644 --- a/src/routes/dashboard/market/+page.svelte +++ b/src/routes/dashboard/market/+page.svelte @@ -20,7 +20,7 @@
{#each data.marketItems as item (item.id)} - + {/each}
{/if} diff --git a/src/routes/dashboard/market/MarketItem.svelte b/src/routes/dashboard/market/MarketItem.svelte index 8572439..c221217 100644 --- a/src/routes/dashboard/market/MarketItem.svelte +++ b/src/routes/dashboard/market/MarketItem.svelte @@ -1,5 +1,5 @@ + + {#if item.minRequiredShopScore > userShopScore} + {item.minRequiredShopScore - userShopScore} more market score needed + {:else if item.computedPrice > userBricks} + {item.computedPrice - userBricks} more bricks needed + {:else} + Buy for {item.computedPrice} bricks + {/if} +
From 023500436ddfed538cc22749a66b8c61bc533156 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Tue, 23 Dec 2025 22:20:41 +0000 Subject: [PATCH 23/25] Improve spacing --- src/routes/dashboard/market/MarketItem.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/dashboard/market/MarketItem.svelte b/src/routes/dashboard/market/MarketItem.svelte index f6206b5..9ca8c2d 100644 --- a/src/routes/dashboard/market/MarketItem.svelte +++ b/src/routes/dashboard/market/MarketItem.svelte @@ -32,7 +32,7 @@ {item.computedPrice} bricks -
+
From 0f3cc095e67c3e1d8d91f9ade349571c72650de4 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Wed, 24 Dec 2025 10:14:50 +0000 Subject: [PATCH 24/25] Add market score to user profile page --- src/routes/dashboard/users/[id]/+page.server.ts | 4 +++- src/routes/dashboard/users/[id]/+page.svelte | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/routes/dashboard/users/[id]/+page.server.ts b/src/routes/dashboard/users/[id]/+page.server.ts index 9f2e2e2..eeae046 100644 --- a/src/routes/dashboard/users/[id]/+page.server.ts +++ b/src/routes/dashboard/users/[id]/+page.server.ts @@ -47,12 +47,14 @@ export async function load({ locals, params }) { slackId: requestedUser.slackId, profilePicture: requestedUser.profilePicture, name: requestedUser.name, - + isPrinter: requestedUser.isPrinter, hasT1Review: requestedUser.hasT1Review, hasT2Review: requestedUser.hasT2Review, hasAdmin: requestedUser.hasAdmin, + shopScore: requestedUser.shopScore, + createdAt: requestedUser.createdAt, lastLoginAt: requestedUser.id === locals.user?.id ? requestedUser.lastLoginAt : null }, diff --git a/src/routes/dashboard/users/[id]/+page.svelte b/src/routes/dashboard/users/[id]/+page.svelte index cba5280..14b5b27 100644 --- a/src/routes/dashboard/users/[id]/+page.svelte +++ b/src/routes/dashboard/users/[id]/+page.svelte @@ -34,6 +34,12 @@ {/if}

+

+ Market score: {data.requestedUser.shopScore} +

+

From a0a95a234e97f05762b1bab3a2b71b51190998a7 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Wed, 24 Dec 2025 22:00:22 +0000 Subject: [PATCH 25/25] Add market ordering user pages --- drizzle/0018_sour_roughhouse.sql | 1 + drizzle/0019_wide_gamora.sql | 1 + drizzle/meta/0018_snapshot.json | 1101 +++++++++++++++++ drizzle/meta/0019_snapshot.json | 1101 +++++++++++++++++ drizzle/meta/_journal.json | 14 + src/lib/server/db/schema.ts | 14 +- .../dashboard/admin/admin/market/+page.svelte | 2 +- .../admin/admin/market/MarketItem.svelte | 4 +- .../{ => item}/[id]/delete/+page.server.ts | 0 .../{ => item}/[id]/delete/+page.svelte | 4 +- .../{ => item}/[id]/edit/+page.server.ts | 0 .../market/{ => item}/[id]/edit/+page.svelte | 4 +- .../market/{ => item}/create/+page.server.ts | 0 .../market/{ => item}/create/+page.svelte | 2 +- src/routes/dashboard/market/MarketItem.svelte | 48 +- .../market/item/[id]/+page.server.ts | 148 +++ .../dashboard/market/item/[id]/+page.svelte | 104 ++ 17 files changed, 2502 insertions(+), 46 deletions(-) create mode 100644 drizzle/0018_sour_roughhouse.sql create mode 100644 drizzle/0019_wide_gamora.sql create mode 100644 drizzle/meta/0018_snapshot.json create mode 100644 drizzle/meta/0019_snapshot.json rename src/routes/dashboard/admin/admin/market/{ => item}/[id]/delete/+page.server.ts (100%) rename src/routes/dashboard/admin/admin/market/{ => item}/[id]/delete/+page.svelte (87%) rename src/routes/dashboard/admin/admin/market/{ => item}/[id]/edit/+page.server.ts (100%) rename src/routes/dashboard/admin/admin/market/{ => item}/[id]/edit/+page.svelte (96%) rename src/routes/dashboard/admin/admin/market/{ => item}/create/+page.server.ts (100%) rename src/routes/dashboard/admin/admin/market/{ => item}/create/+page.svelte (98%) create mode 100644 src/routes/dashboard/market/item/[id]/+page.server.ts create mode 100644 src/routes/dashboard/market/item/[id]/+page.svelte diff --git a/drizzle/0018_sour_roughhouse.sql b/drizzle/0018_sour_roughhouse.sql new file mode 100644 index 0000000..4a680e5 --- /dev/null +++ b/drizzle/0018_sour_roughhouse.sql @@ -0,0 +1 @@ +ALTER TABLE "market_item_order" ALTER COLUMN "notes" DROP NOT NULL; \ No newline at end of file diff --git a/drizzle/0019_wide_gamora.sql b/drizzle/0019_wide_gamora.sql new file mode 100644 index 0000000..11031a9 --- /dev/null +++ b/drizzle/0019_wide_gamora.sql @@ -0,0 +1 @@ +ALTER TABLE "market_item_order" ALTER COLUMN "userId" SET NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0018_snapshot.json b/drizzle/meta/0018_snapshot.json new file mode 100644 index 0000000..9268cb2 --- /dev/null +++ b/drizzle/meta/0018_snapshot.json @@ -0,0 +1,1101 @@ +{ + "id": "f03cfd63-e7c6-44fa-902c-2f9237b9ee96", + "prevId": "c1769f02-cca9-40e9-afe3-ed329a1f74c7", + "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.market_item": { + "name": "market_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "minRequiredShopScore": { + "name": "minRequiredShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "minShopScore": { + "name": "minShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxShopScore": { + "name": "maxShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxPrice": { + "name": "maxPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "minPrice": { + "name": "minPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "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": { + "market_item_createdBy_user_id_fk": { + "name": "market_item_createdBy_user_id_fk", + "tableFrom": "market_item", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.market_item_order": { + "name": "market_item_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "addressId": { + "name": "addressId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bricksPaid": { + "name": "bricksPaid", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "market_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "userNotes": { + "name": "userNotes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "market_item_order_userId_user_id_fk": { + "name": "market_item_order_userId_user_id_fk", + "tableFrom": "market_item_order", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "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.market_order_status": { + "name": "market_order_status", + "schema": "public", + "values": [ + "awaiting_approval", + "approved", + "fulfilled", + "denied", + "refunded" + ] + }, + "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/0019_snapshot.json b/drizzle/meta/0019_snapshot.json new file mode 100644 index 0000000..a1e85ba --- /dev/null +++ b/drizzle/meta/0019_snapshot.json @@ -0,0 +1,1101 @@ +{ + "id": "2406dbb1-cdda-4c04-b6f1-363172327705", + "prevId": "f03cfd63-e7c6-44fa-902c-2f9237b9ee96", + "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.market_item": { + "name": "market_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "minRequiredShopScore": { + "name": "minRequiredShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "minShopScore": { + "name": "minShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxShopScore": { + "name": "maxShopScore", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "maxPrice": { + "name": "maxPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "minPrice": { + "name": "minPrice", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "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": { + "market_item_createdBy_user_id_fk": { + "name": "market_item_createdBy_user_id_fk", + "tableFrom": "market_item", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.market_item_order": { + "name": "market_item_order", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "addressId": { + "name": "addressId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bricksPaid": { + "name": "bricksPaid", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "market_order_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_approval'" + }, + "userNotes": { + "name": "userNotes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "market_item_order_userId_user_id_fk": { + "name": "market_item_order_userId_user_id_fk", + "tableFrom": "market_item_order", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "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.market_order_status": { + "name": "market_order_status", + "schema": "public", + "values": [ + "awaiting_approval", + "approved", + "fulfilled", + "denied", + "refunded" + ] + }, + "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 14a627f..36ecf62 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -127,6 +127,20 @@ "when": 1766436733390, "tag": "0017_long_greymalkin", "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1766613036394, + "tag": "0018_sour_roughhouse", + "breakpoints": true + }, + { + "idx": 19, + "version": "7", + "when": 1766613085822, + "tag": "0019_wide_gamora", + "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 f5c2600..f34ab02 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -213,16 +213,16 @@ export const marketItem = pgTable('market_item', { name: text().notNull(), description: text().notNull(), image: text().notNull(), - + minRequiredShopScore: integer().notNull().default(0), - + minShopScore: integer().notNull(), maxShopScore: integer().notNull(), // Score after which price becomes constant maxPrice: integer().notNull(), minPrice: integer().notNull(), - + isPublic: boolean().notNull().default(false), - + deleted: boolean().notNull().default(false), createdAt: timestamp().notNull().defaultNow(), updatedAt: timestamp().notNull().defaultNow() @@ -238,14 +238,14 @@ export const marketOrderStatus = pgEnum('market_order_status', [ export const marketItemOrder = pgTable('market_item_order', { id: serial().primaryKey(), - userId: integer().references(() => user.id), + userId: integer().references(() => user.id).notNull(), addressId: text().notNull(), bricksPaid: integer().notNull(), - + status: marketOrderStatus().notNull().default('awaiting_approval'), userNotes: text().notNull(), - notes: text().notNull(), + notes: text(), // stuff like tracking code, shown to user deleted: boolean().notNull().default(false), createdAt: timestamp().notNull().defaultNow() diff --git a/src/routes/dashboard/admin/admin/market/+page.svelte b/src/routes/dashboard/admin/admin/market/+page.svelte index 147114d..4ebb74f 100644 --- a/src/routes/dashboard/admin/admin/market/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/+page.svelte @@ -32,7 +32,7 @@

Market

diff --git a/src/routes/dashboard/admin/admin/market/MarketItem.svelte b/src/routes/dashboard/admin/admin/market/MarketItem.svelte index b67e0a1..2e4a587 100644 --- a/src/routes/dashboard/admin/admin/market/MarketItem.svelte +++ b/src/routes/dashboard/admin/admin/market/MarketItem.svelte @@ -15,8 +15,8 @@ {#if showButtons} {/if}
diff --git a/src/routes/dashboard/admin/admin/market/[id]/delete/+page.server.ts b/src/routes/dashboard/admin/admin/market/item/[id]/delete/+page.server.ts similarity index 100% rename from src/routes/dashboard/admin/admin/market/[id]/delete/+page.server.ts rename to src/routes/dashboard/admin/admin/market/item/[id]/delete/+page.server.ts diff --git a/src/routes/dashboard/admin/admin/market/[id]/delete/+page.svelte b/src/routes/dashboard/admin/admin/market/item/[id]/delete/+page.svelte similarity index 87% rename from src/routes/dashboard/admin/admin/market/[id]/delete/+page.svelte rename to src/routes/dashboard/admin/admin/market/item/[id]/delete/+page.svelte index b83db1c..133f51a 100644 --- a/src/routes/dashboard/admin/admin/market/[id]/delete/+page.svelte +++ b/src/routes/dashboard/admin/admin/market/item/[id]/delete/+page.svelte @@ -1,7 +1,7 @@ - -
diff --git a/src/routes/dashboard/market/item/[id]/+page.server.ts b/src/routes/dashboard/market/item/[id]/+page.server.ts new file mode 100644 index 0000000..cb3d970 --- /dev/null +++ b/src/routes/dashboard/market/item/[id]/+page.server.ts @@ -0,0 +1,148 @@ +import { db } from '$lib/server/db/index.js'; +import { marketItem, marketItemOrder, user } from '$lib/server/db/schema.js'; +import { decrypt } from '$lib/server/encryption'; +import { getUserData } from '$lib/server/idvUserData'; +import { calculateMarketPrice } from '$lib/utils'; +import { error, redirect } from '@sveltejs/kit'; +import { eq, and } from 'drizzle-orm'; +import type { Actions } from './$types'; + +export async function load({ locals, params }) { + if (!locals.user) { + throw error(500); + } + + const id: number = parseInt(params.id); + + const itemWithPrice = await getItemWithPrice(id, locals); + + let userDataError = false; + let addresses = null; + + if (locals.user.idvToken) { + const token = decrypt(locals.user.idvToken); + let userData = null; + + try { + userData = await getUserData(token); + } catch { + userDataError = true; + } + + addresses = userData?.addresses; + } else { + userDataError = true; + } + + return { + marketItem: itemWithPrice, + addresses, + userDataError + }; +} + +export const actions = { + default: async ({ locals, request, params }) => { + if (!locals.user) { + throw error(500); + } + + const id: number = parseInt(params.id); + + const data = await request.formData(); + const addressId = data.get('address')?.toString(); + const notes = data.get('notes')?.toString(); + + if (!addressId) { + throw error(400, { message: 'invalid address id' }); + } + + if (notes === null || notes === undefined || notes.length > 10000) { + throw error(400, { message: 'stop writing so much in notes' }); + } + + const itemWithPrice = await getItemWithPrice(id, locals); + + const token = decrypt(locals.user.idvToken!); + let userData = null; + + try { + userData = await getUserData(token); + } catch { + throw error(403, { message: 'failed to fetch address, try logging out and back in' }); + } + + const addresses: [{ id: string | null }] | null = userData?.addresses; + + if (!addresses || !addresses.length || addresses.length <= 0) { + throw error(400, { message: 'no addresses added on auth.hackclub.com' }); + } + + const address = addresses.find((addr) => addr.id! === addressId); + + if (!address) { + throw error(400, { message: 'chosen address not found' }); + } + + // Check if user can afford + if ( + itemWithPrice.computedPrice > locals.user.brick || + itemWithPrice.minRequiredShopScore > locals.user.shopScore + ) { + throw error(403, { message: "you can't afford this" }); + } + + await db + .update(user) + .set({ + brick: locals.user.brick - itemWithPrice.computedPrice + }) + .where(eq(user.id, locals.user.id)); + + await db.insert(marketItemOrder).values({ + userId: locals.user.id, + addressId, + bricksPaid: itemWithPrice.computedPrice, + userNotes: notes + }); + + // TODO: change this to orders page + return redirect(302, '/dashboard/market'); + } +} satisfies Actions; + +async function getItemWithPrice(id: number, locals: { user: { shopScore: number } | null }) { + const [item] = await db + .select({ + id: marketItem.id, + name: marketItem.name, + description: marketItem.description, + image: marketItem.image, + minPrice: marketItem.minPrice, + maxPrice: marketItem.maxPrice, + minShopScore: marketItem.minShopScore, + maxShopScore: marketItem.maxShopScore, + minRequiredShopScore: marketItem.minRequiredShopScore + }) + .from(marketItem) + .where(and(eq(marketItem.deleted, false), eq(marketItem.isPublic, true), eq(marketItem.id, id))) + .limit(1); + + if (!item) { + throw error(404, { message: 'market item not found' }); + } + + const shopScore = locals.user!.shopScore; + const computedPrice = calculateMarketPrice( + item.minPrice, + item.maxPrice, + item.minShopScore, + item.maxShopScore, + shopScore + ); + const discountAmount = 1 - computedPrice / item.maxPrice; + + const itemWithPrice = { ...item, computedPrice, discountAmount }; + + return itemWithPrice; +} diff --git a/src/routes/dashboard/market/item/[id]/+page.svelte b/src/routes/dashboard/market/item/[id]/+page.svelte new file mode 100644 index 0000000..6263c81 --- /dev/null +++ b/src/routes/dashboard/market/item/[id]/+page.svelte @@ -0,0 +1,104 @@ + + + + +

Buy?

+ +
+
+ +
+ +
+

Are you sure you want to buy this?

+

It'll cost you {data.marketItem.computedPrice} bricks

+ +
{ + formPending = true; + return async ({ update }) => { + await update(); + formPending = false; + }; + }} + > + + + + +
+ + +
+
+