From 32188d9428991e9f0bc6d221bc2eca43d321de29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Balogh=20Barnab=C3=A1s?= Date: Fri, 6 Feb 2026 18:14:26 +0100 Subject: [PATCH 1/3] Add T2 review logs to review pages --- .../dashboard/admin/ReviewHistory.svelte | 20 +++++++++++++++++ .../admin/getReviewHistory.server.ts | 22 +++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/routes/dashboard/admin/ReviewHistory.svelte b/src/routes/dashboard/admin/ReviewHistory.svelte index 5562e77..bb05967 100644 --- a/src/routes/dashboard/admin/ReviewHistory.svelte +++ b/src/routes/dashboard/admin/ReviewHistory.svelte @@ -34,4 +34,24 @@

{/each} +

T2 reviews

+ {#each reviews.t2Reviews as review} +
+ {#if review.notes} +

Notes: {review.notes}

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

Feedback: {review.feedback}

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

Image: {review.image}

+ {/if} +

+ Market score per hour: {review.shopScoreMultiplier} +

+

+ by {review.user.name} +

+
+ {/each} diff --git a/src/routes/dashboard/admin/getReviewHistory.server.ts b/src/routes/dashboard/admin/getReviewHistory.server.ts index 6bf3750..40d4f33 100644 --- a/src/routes/dashboard/admin/getReviewHistory.server.ts +++ b/src/routes/dashboard/admin/getReviewHistory.server.ts @@ -1,5 +1,5 @@ import { db } from '$lib/server/db'; -import { legionReview, t1Review, user } from '$lib/server/db/schema'; +import { legionReview, t1Review, t2Review, user } from '$lib/server/db/schema'; import { eq, asc } from 'drizzle-orm'; export async function getReviewHistory(id: number) { @@ -36,8 +36,26 @@ export async function getReviewHistory(id: number) { .where(eq(legionReview.projectId, id)) .orderBy(asc(legionReview.timestamp)); + const t2Reviews = await db + .select({ + user: { + id: user.id, + name: user.name + }, + notes: t2Review.notes, + feedback: t2Review.feedback, + image: t2Review.image, + shopScoreMultiplier: t2Review.shopScoreMultiplier, + timestamp: t2Review.timestamp + }) + .from(t2Review) + .innerJoin(user, eq(user.id, t2Review.userId)) + .where(eq(t2Review.projectId, id)) + .orderBy(asc(t2Review.timestamp)); + return { t1Reviews, - legionReviews + legionReviews, + t2Reviews }; } From 95c8a6fdae7aa62ec3e5dffff0711d630f2babe2 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Sat, 7 Feb 2026 10:17:46 +0000 Subject: [PATCH 2/3] Allow hackatime check bypass if they don't have a hackatime account (some people at my school had a problem with this) --- src/routes/auth/callback/+server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/auth/callback/+server.ts b/src/routes/auth/callback/+server.ts index 799322e..ce5fc36 100644 --- a/src/routes/auth/callback/+server.ts +++ b/src/routes/auth/callback/+server.ts @@ -138,8 +138,8 @@ export async function GET(event) { )['trust_level']; if (!hackatimeTrust) { - console.error(); - return redirect(302, '/auth/create-hackatime-account'); + // console.error(); + // return redirect(302, '/auth/create-hackatime-account'); // return error(503, { // message: 'failed to fetch hackatime trust factor, please try again later' // }); From 07797b951417494585d11d870e1228028f976314 Mon Sep 17 00:00:00 2001 From: Arca Ege Cengiz Date: Sat, 7 Feb 2026 22:25:19 +0000 Subject: [PATCH 3/3] Add sticker fulfilment admin page --- drizzle/0029_cultured_the_fury.sql | 1 + drizzle/meta/0029_snapshot.json | 1414 +++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/server/db/schema.ts | 6 +- src/routes/dashboard/admin/admin/+page.svelte | 11 +- .../admin/admin/stickers/+page.server.ts | 42 + .../admin/admin/stickers/+page.svelte | 65 + .../admin/admin/stickers/[id]/+page.server.ts | 123 ++ .../admin/admin/stickers/[id]/+page.svelte | 188 +++ .../admin/admin/users/[id]/+page.svelte | 4 + 10 files changed, 1857 insertions(+), 4 deletions(-) create mode 100644 drizzle/0029_cultured_the_fury.sql create mode 100644 drizzle/meta/0029_snapshot.json create mode 100644 src/routes/dashboard/admin/admin/stickers/+page.server.ts create mode 100644 src/routes/dashboard/admin/admin/stickers/+page.svelte create mode 100644 src/routes/dashboard/admin/admin/stickers/[id]/+page.server.ts create mode 100644 src/routes/dashboard/admin/admin/stickers/[id]/+page.svelte diff --git a/drizzle/0029_cultured_the_fury.sql b/drizzle/0029_cultured_the_fury.sql new file mode 100644 index 0000000..0469ac2 --- /dev/null +++ b/drizzle/0029_cultured_the_fury.sql @@ -0,0 +1 @@ +ALTER TABLE "user" ADD COLUMN "stickersShipped" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0029_snapshot.json b/drizzle/meta/0029_snapshot.json new file mode 100644 index 0000000..ad24486 --- /dev/null +++ b/drizzle/meta/0029_snapshot.json @@ -0,0 +1,1414 @@ +{ + "id": "4a16de7d-9818-49ae-a154-25863c54bd84", + "prevId": "fabc3135-7dc2-4e68-bd11-f63e1d94b5dc", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.club": { + "name": "club", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "joinCode": { + "name": "joinCode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "club_name_unique": { + "name": "club_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.club_membership": { + "name": "club_membership", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "clubId": { + "name": "clubId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "club_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "club_membership_clubId_club_id_fk": { + "name": "club_membership_clubId_club_id_fk", + "tableFrom": "club_membership", + "tableTo": "club", + "columnsFrom": [ + "clubId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "club_membership_userId_user_id_fk": { + "name": "club_membership_userId_user_id_fk", + "tableFrom": "club_membership", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "club_membership_userId_unique": { + "name": "club_membership_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "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.impersonate_audit_log": { + "name": "impersonate_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "adminUserId": { + "name": "adminUserId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "targetUserId": { + "name": "targetUserId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "impersonate_audit_log_adminUserId_user_id_fk": { + "name": "impersonate_audit_log_adminUserId_user_id_fk", + "tableFrom": "impersonate_audit_log", + "tableTo": "user", + "columnsFrom": [ + "adminUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "impersonate_audit_log_targetUserId_user_id_fk": { + "name": "impersonate_audit_log_targetUserId_user_id_fk", + "tableFrom": "impersonate_audit_log", + "tableTo": "user", + "columnsFrom": [ + "targetUserId" + ], + "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 + }, + "marketItemId": { + "name": "marketItemId", + "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" + }, + "market_item_order_marketItemId_market_item_id_fk": { + "name": "market_item_order_marketItemId_market_item_id_fk", + "tableFrom": "market_item_order", + "tableTo": "market_item", + "columnsFrom": [ + "marketItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ovenpheus_log": { + "name": "ovenpheus_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "clay": { + "name": "clay", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "bricksReceived": { + "name": "bricksReceived", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ovenpheus_log_userId_user_id_fk": { + "name": "ovenpheus_log_userId_user_id_fk", + "tableFrom": "ovenpheus_log", + "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 + }, + "clubId": { + "name": "clubId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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" + }, + "ship_clubId_club_id_fk": { + "name": "ship_clubId_club_id_fk", + "tableFrom": "ship", + "tableTo": "club", + "columnsFrom": [ + "clubId" + ], + "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 + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shopScoreMultiplier": { + "name": "shopScoreMultiplier", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "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 + }, + "isPrinter": { + "name": "isPrinter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "hasAdmin": { + "name": "hasAdmin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "referralId": { + "name": "referralId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stickersShipped": { + "name": "stickersShipped", + "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.club_role": { + "name": "club_role", + "schema": "public", + "values": [ + "leader", + "member" + ] + }, + "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", + "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 8c06a02..9acf297 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -204,6 +204,13 @@ "when": 1769368148290, "tag": "0028_illegal_robbie_robertson", "breakpoints": true + }, + { + "idx": 29, + "version": "7", + "when": 1770461520828, + "tag": "0029_cultured_the_fury", + "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 90e39ec..a9b07c6 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -34,13 +34,13 @@ export const user = pgTable('user', { hasT1Review: boolean().notNull().default(false), // Has access to t1 review hasT2Review: boolean().notNull().default(false), // Has access to t2 review - - hasAdmin: boolean().notNull().default(false), // Has access to admin section - isPrinter: boolean().notNull().default(false), // Is a printer + hasAdmin: boolean().notNull().default(false), // Has access to admin section referralId: text(), + stickersShipped: boolean().notNull().default(false), // Stickers/keyring fulfilled + createdAt: timestamp().notNull().defaultNow(), // Account creation timestamp lastLoginAt: timestamp().notNull().defaultNow() // Last login timestamp }); diff --git a/src/routes/dashboard/admin/admin/+page.svelte b/src/routes/dashboard/admin/admin/+page.svelte index e2e3394..67db8df 100644 --- a/src/routes/dashboard/admin/admin/+page.svelte +++ b/src/routes/dashboard/admin/admin/+page.svelte @@ -1,6 +1,6 @@ @@ -47,6 +47,15 @@

Item orders

+ +
+ +
+

Stickers/keyrings

+
`(array_agg(${project.createdAt} ORDER BY ${project.createdAt} ASC))[1]` + .mapWith((val) => new Date(val)) + .as('first_created_at') + }) + .from(project) + .innerJoin(user, eq(project.userId, user.id)) + .where( + and( + eq(project.status, 'finalized'), + eq(project.deleted, false), + eq(user.stickersShipped, false) + ) + ) + .groupBy(user.id) + .orderBy(asc(sql`(array_agg(${project.createdAt} ORDER BY ${project.createdAt} ASC))[1]`)); + + return { + users + }; +} diff --git a/src/routes/dashboard/admin/admin/stickers/+page.svelte b/src/routes/dashboard/admin/admin/stickers/+page.svelte new file mode 100644 index 0000000..4cabc4d --- /dev/null +++ b/src/routes/dashboard/admin/admin/stickers/+page.svelte @@ -0,0 +1,65 @@ + + + + +
+
+

Stickers/keyrings

+
+ +

Showing {filteredUsers.length} users

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

+ No users found matching the filter heavysob +

+
+
+ {:else} +
+ {#each filteredUsers as user} +
+ +

+ {user.name} +

+ + {user.slackId} + +
+

Project created

+ + {relativeDate(user.projectCreatedAt)} + +
+

+ {Math.round(user.clay * 10) / 10} clay, {Math.round(user.brick * 10) / 10} brick, {Math.round( + user.shopScore * 10 + ) / 10} market +

+
+ {/each} +
+ {/if} +
diff --git a/src/routes/dashboard/admin/admin/stickers/[id]/+page.server.ts b/src/routes/dashboard/admin/admin/stickers/[id]/+page.server.ts new file mode 100644 index 0000000..3b42ebc --- /dev/null +++ b/src/routes/dashboard/admin/admin/stickers/[id]/+page.server.ts @@ -0,0 +1,123 @@ +import { db } from '$lib/server/db/index.js'; +import { user, session } from '$lib/server/db/schema.js'; +import { error } from '@sveltejs/kit'; +import { eq } from 'drizzle-orm'; +import type { Actions } from './$types'; +import { decrypt } from '$lib/server/encryption'; +import { getUserData } from '$lib/server/idvUserData'; + +export async function load({ locals, params }) { + if (!locals.user) { + throw error(500); + } + if (!locals.user.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const id: number = parseInt(params.id); + + const [queriedUser] = await db.select().from(user).where(eq(user.id, id)); + + if (!queriedUser) { + throw error(404, { message: 'user not found' }); + } + + let piiSuccess = true; + + if (!queriedUser.idvToken) { + piiSuccess = false; + } + + let userData; + + try { + const token = decrypt(queriedUser.idvToken ?? ''); + userData = await getUserData(token); + } catch { + piiSuccess = false; + } + + const { first_name, last_name, primary_email, birthday, phone_number, addresses } = userData; + + const address = addresses?.find((address: { primary: boolean }) => address.primary); + + const pii = piiSuccess + ? { + first_name, + last_name, + primary_email, + phone_number, + birthday, + address + } + : null; + + return { + queriedUser, + pii + }; +} + +export const actions = { + logout: async ({ locals, params }) => { + if (!locals.user) { + throw error(500); + } + if (!locals.user.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const id: number = parseInt(params.id); + + const [queriedUser] = await db.select().from(user).where(eq(user.id, id)); + + if (!queriedUser) { + throw error(404, { message: 'user not found' }); + } + + // Log out user + await db.delete(session).where(eq(session.userId, id)); + + return { + queriedUser + }; + }, + + fulfil: async ({ locals, params }) => { + if (!locals.user) { + throw error(500); + } + if (!locals.user.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const id: number = parseInt(params.id); + + const [queriedUser] = await db.select().from(user).where(eq(user.id, id)); + + if (!queriedUser) { + throw error(404, { message: 'user not found' }); + } + + await db.update(user).set({ stickersShipped: true }).where(eq(user.id, id)); + }, + + unfulfil: async ({ locals, params }) => { + if (!locals.user) { + throw error(500); + } + if (!locals.user.hasAdmin) { + throw error(403, { message: 'oi get out' }); + } + + const id: number = parseInt(params.id); + + const [queriedUser] = await db.select().from(user).where(eq(user.id, id)); + + if (!queriedUser) { + throw error(404, { message: 'user not found' }); + } + + await db.update(user).set({ stickersShipped: false }).where(eq(user.id, id)); + } +} satisfies Actions; diff --git a/src/routes/dashboard/admin/admin/stickers/[id]/+page.svelte b/src/routes/dashboard/admin/admin/stickers/[id]/+page.svelte new file mode 100644 index 0000000..7acad8e --- /dev/null +++ b/src/routes/dashboard/admin/admin/stickers/[id]/+page.svelte @@ -0,0 +1,188 @@ + + + + +
+
+
+

{user.name}

+ +
+ user profile +
+ +
+ Public profile page + Admin profile page + +
+
{ + logoutPending = true; + return async ({ update }) => { + await update(); + logoutPending = false; + }; + }} + > + +
+
+
+ +

User details

+ +
+ + {user.slackId} + + + {user.idvId} + + + {user.trust} + + + {user.hackatimeTrust} + + + {user.hasBasePrinter ? 'Yes' : 'No'} + + + {user.stickersShipped ? 'Yes' : 'No'} + + + + {relativeDate(user.createdAt)} + + + + + {relativeDate(user.lastLoginAt)} + + + + {user.referralId ?? 'None'} + + + {user.clay} + + + {user.brick} + + + {user.shopScore} + +
+ +

Fulfilment details

+
+ {#if data.pii} +
+ + {data.pii.first_name} + + + {data.pii.last_name} + + + {data.pii.primary_email} + + + {data.pii.phone_number} + + + {data.pii.birthday} + +
+ +

address

+ +
+ {#if data.pii.address} +
+

+ {data.pii.address.id} +

+

+ {data.pii.address.first_name} + {data.pii.address.last_name} +

+

+ {data.pii.address.line_1} +

+ {#if data.pii.address.line_2} +

+ {data.pii.address.line_2} +

+ {/if} +

+ {data.pii.address.city}, {data.pii.address.state} + {data.pii.address.postal_code} +

+

+ {data.pii.address.country} +

+
+ {:else} +

No address defined

+ {/if} +
+ {:else} +

Failed to fetch PII, ask them to re-login

+ {/if} +
+ +

Clicky button

+
+
{ + formPending = true; + return async ({ update }) => { + await update(); + formPending = false; + }; + }} + > + +
+
+
+
+
diff --git a/src/routes/dashboard/admin/admin/users/[id]/+page.svelte b/src/routes/dashboard/admin/admin/users/[id]/+page.svelte index c5372b7..523eac8 100644 --- a/src/routes/dashboard/admin/admin/users/[id]/+page.svelte +++ b/src/routes/dashboard/admin/admin/users/[id]/+page.svelte @@ -34,6 +34,7 @@
Public profile page + Sticker fulfilment page
{user.hasBasePrinter ? 'Yes' : 'No'} + + {user.stickersShipped ? 'Yes' : 'No'} + {relativeDate(user.createdAt)}