From b8173bd8f88c99333c60b60d77fadb2370491fa6 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Wed, 29 Apr 2026 00:16:43 +0800 Subject: [PATCH] feat: non-Steam game editing API, createdBy tracking, and source badges in search --- app/search/page.tsx | 10 + drizzle/0013_supreme_purple_man.sql | 1 + drizzle/meta/0013_snapshot.json | 1726 +++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + lib/api/games-manual.ts | 68 +- lib/db/schema/games.ts | 1 + 6 files changed, 1812 insertions(+), 1 deletion(-) create mode 100644 drizzle/0013_supreme_purple_man.sql create mode 100644 drizzle/meta/0013_snapshot.json diff --git a/app/search/page.tsx b/app/search/page.tsx index 2961e4b..22218c5 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -267,6 +267,16 @@ function SearchResultCard({

{result.title}

+ {!isLocal && ( + + Steam + + )} + {isLocal && result.source !== "steam" && ( + + {result.source} + + )} {result.isRawPerformer && ( ⚡ RAW PERFORMER diff --git a/drizzle/0013_supreme_purple_man.sql b/drizzle/0013_supreme_purple_man.sql new file mode 100644 index 0000000..2a2dad7 --- /dev/null +++ b/drizzle/0013_supreme_purple_man.sql @@ -0,0 +1 @@ +ALTER TABLE "games" ADD COLUMN "created_by" text; \ No newline at end of file diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..bc4c289 --- /dev/null +++ b/drizzle/meta/0013_snapshot.json @@ -0,0 +1,1726 @@ +{ + "id": "ceee987f-0c28-452e-ae54-0be7d4f05425", + "prevId": "9ad774e3-fb62-48e7-9b48-69fd0776b46d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey": { + "name": "passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "passkey_credentialID_idx": { + "name": "passkey_credentialID_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "passkey_user_id_user_id_fk": { + "name": "passkey_user_id_user_id_fk", + "tableFrom": "passkey", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_login_method": { + "name": "last_login_method", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_comments": { + "name": "game_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "upvotes": { + "name": "upvotes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_removed": { + "name": "is_removed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "comments_game_created_idx": { + "name": "comments_game_created_idx", + "columns": [ + { + "expression": "game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "comments_parent_idx": { + "name": "comments_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_comments_game_id_games_id_fk": { + "name": "game_comments_game_id_games_id_fk", + "tableFrom": "game_comments", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_comments_user_id_user_id_fk": { + "name": "game_comments_user_id_user_id_fk", + "tableFrom": "game_comments", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_comments_parent_id_game_comments_id_fk": { + "name": "game_comments_parent_id_game_comments_id_fk", + "tableFrom": "game_comments", + "tableTo": "game_comments", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_platform_support": { + "name": "game_platform_support", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hardware_slug": { + "name": "hardware_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_supported": { + "name": "is_supported", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "proton_status": { + "name": "proton_status", + "type": "proton_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "anti_cheat_relevant": { + "name": "anti_cheat_relevant", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anti_cheat_name": { + "name": "anti_cheat_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "anti_cheat_version": { + "name": "anti_cheat_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "anti_cheat_status": { + "name": "anti_cheat_status", + "type": "anti_cheat_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "game_platform_support_game_id_games_id_fk": { + "name": "game_platform_support_game_id_games_id_fk", + "tableFrom": "game_platform_support", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_platform_support_hardware_slug_hardware_slug_fk": { + "name": "game_platform_support_hardware_slug_hardware_slug_fk", + "tableFrom": "game_platform_support", + "tableTo": "hardware", + "columnsFrom": [ + "hardware_slug" + ], + "columnsTo": [ + "slug" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_hardware_unique": { + "name": "game_hardware_unique", + "nullsNotDistinct": false, + "columns": [ + "game_id", + "hardware_slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_versions": { + "name": "game_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_string": { + "name": "version_string", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_latest": { + "name": "is_latest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "game_versions_game_id_games_id_fk": { + "name": "game_versions_game_id_games_id_fk", + "tableFrom": "game_versions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_build_unique": { + "name": "game_build_unique", + "nullsNotDistinct": false, + "columns": [ + "game_id", + "build_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "game_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'steam'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publisher": { + "name": "publisher", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developer": { + "name": "developer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_image": { + "name": "header_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capsule_image": { + "name": "capsule_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "store_url": { + "name": "store_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "online_multiplayer_status": { + "name": "online_multiplayer_status", + "type": "online_multiplayer_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "system_requirements": { + "name": "system_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metacritic_score": { + "name": "metacritic_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metacritic_url": { + "name": "metacritic_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "recommendations_total": { + "name": "recommendations_total", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "price_current": { + "name": "price_current", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "price_initial": { + "name": "price_initial", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "price_currency": { + "name": "price_currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "release_date": { + "name": "release_date", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "platforms": { + "name": "platforms", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_sync": { + "name": "last_sync", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'pending'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "games_source_idx": { + "name": "games_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_steam_app_id_unique": { + "name": "games_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": [ + "steam_app_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hardware": { + "name": "hardware", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.performance_entries": { + "name": "performance_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version_id": { + "name": "version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hardware_slug": { + "name": "hardware_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fps_avg": { + "name": "fps_avg", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "fps_low": { + "name": "fps_low", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fps_one_percent_low": { + "name": "fps_one_percent_low", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fps_high": { + "name": "fps_high", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "proton_version": { + "name": "proton_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "os_version": { + "name": "os_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upscaler_type": { + "name": "upscaler_type", + "type": "upscaler_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "upscaler_version": { + "name": "upscaler_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "frame_gen_method": { + "name": "frame_gen_method", + "type": "frame_gen_method", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "load_time_ssd": { + "name": "load_time_ssd", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "load_time_sd": { + "name": "load_time_sd", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "launch_options": { + "name": "launch_options", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "estimated_battery_min": { + "name": "estimated_battery_min", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "custom_system": { + "name": "custom_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_removed": { + "name": "is_removed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "removed_reason": { + "name": "removed_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upvotes": { + "name": "upvotes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "downvotes": { + "name": "downvotes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "verified_by": { + "name": "verified_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "perf_hardware_upscaler_idx": { + "name": "perf_hardware_upscaler_idx", + "columns": [ + { + "expression": "hardware_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "upscaler_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "perf_version_idx": { + "name": "perf_version_idx", + "columns": [ + { + "expression": "version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "perf_user_idx": { + "name": "perf_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "performance_entries_version_id_game_versions_id_fk": { + "name": "performance_entries_version_id_game_versions_id_fk", + "tableFrom": "performance_entries", + "tableTo": "game_versions", + "columnsFrom": [ + "version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "performance_entries_hardware_slug_hardware_slug_fk": { + "name": "performance_entries_hardware_slug_hardware_slug_fk", + "tableFrom": "performance_entries", + "tableTo": "hardware", + "columnsFrom": [ + "hardware_slug" + ], + "columnsTo": [ + "slug" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "performance_entries_user_id_user_id_fk": { + "name": "performance_entries_user_id_user_id_fk", + "tableFrom": "performance_entries", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "performance_entries_verified_by_user_id_fk": { + "name": "performance_entries_verified_by_user_id_fk", + "tableFrom": "performance_entries", + "tableTo": "user", + "columnsFrom": [ + "verified_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_games": { + "name": "saved_games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "saved_games_user_id_user_id_fk": { + "name": "saved_games_user_id_user_id_fk", + "tableFrom": "saved_games", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saved_games_game_id_games_id_fk": { + "name": "saved_games_game_id_games_id_fk", + "tableFrom": "saved_games", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "saved_games_user_game_unique": { + "name": "saved_games_user_game_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "game_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reports": { + "name": "reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "entry_id": { + "name": "entry_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reporter_id": { + "name": "reporter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "report_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "report_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reports_entry_reporter_unique": { + "name": "reports_entry_reporter_unique", + "columns": [ + { + "expression": "entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reporter_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reports_entry_id_performance_entries_id_fk": { + "name": "reports_entry_id_performance_entries_id_fk", + "tableFrom": "reports", + "tableTo": "performance_entries", + "columnsFrom": [ + "entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reports_reporter_id_user_id_fk": { + "name": "reports_reporter_id_user_id_fk", + "tableFrom": "reports", + "tableTo": "user", + "columnsFrom": [ + "reporter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.anti_cheat_status": { + "name": "anti_cheat_status", + "schema": "public", + "values": [ + "none", + "supported", + "unsupported", + "unknown" + ] + }, + "public.proton_status": { + "name": "proton_status", + "schema": "public", + "values": [ + "native", + "proton", + "unsupported", + "unknown" + ] + }, + "public.game_source": { + "name": "game_source", + "schema": "public", + "values": [ + "steam", + "manual", + "gog", + "epic" + ] + }, + "public.online_multiplayer_status": { + "name": "online_multiplayer_status", + "schema": "public", + "values": [ + "none", + "supported", + "unknown" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "handheld", + "console" + ] + }, + "public.frame_gen_method": { + "name": "frame_gen_method", + "schema": "public", + "values": [ + "none", + "fsr_fg", + "dlss_fg", + "lsfg", + "other" + ] + }, + "public.upscaler_type": { + "name": "upscaler_type", + "schema": "public", + "values": [ + "none", + "fsr", + "dlss", + "xess", + "lsfg", + "other" + ] + }, + "public.report_reason": { + "name": "report_reason", + "schema": "public", + "values": [ + "inaccurate", + "spam", + "inappropriate", + "other" + ] + }, + "public.report_status": { + "name": "report_status", + "schema": "public", + "values": [ + "open", + "reviewed", + "dismissed" + ] + } + }, + "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 062fac3..d8e56fe 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1777389996351, "tag": "0012_clever_thunderbolt_ross", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1777392960312, + "tag": "0013_supreme_purple_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/lib/api/games-manual.ts b/lib/api/games-manual.ts index b436f1e..a276a1b 100644 --- a/lib/api/games-manual.ts +++ b/lib/api/games-manual.ts @@ -1,7 +1,7 @@ import { Elysia, t } from "elysia" import { db } from "@/lib/db/index" import { games, gameVersions, gamePlatformSupport } from "@/lib/db/schema" -import { ilike } from "drizzle-orm" +import { ilike, eq } from "drizzle-orm" import { requireRole } from "@/lib/auth/guard" export const gamesManualRoutes = new Elysia({ prefix: "/games" }) @@ -45,6 +45,7 @@ export const gamesManualRoutes = new Elysia({ prefix: "/games" }) storeUrl: body.storeUrl || null, genres: body.genres || null, releaseDate: body.releaseDate || null, + createdBy: guard.user.id, }) .returning() @@ -93,3 +94,68 @@ export const gamesManualRoutes = new Elysia({ prefix: "/games" }) }), } ) + .put( + "/:gameId/manual", + async ({ request, params, body, set }) => { + const guard = await requireRole(request.headers, ["user", "contributor", "admin"]) + if (!guard.ok) { + set.status = guard.status + return { error: guard.error } + } + + const [game] = await db + .select() + .from(games) + .where(eq(games.id, params.gameId)) + .limit(1) + + if (!game) { + set.status = 404 + return { error: "Game not found" } + } + + if (game.source === "steam") { + set.status = 403 + return { error: "Steam games are synced automatically and cannot be manually edited" } + } + + // Only creator or admin can edit + if (game.createdBy && game.createdBy !== guard.user.id && guard.user.role !== "admin") { + set.status = 403 + return { error: "Only the creator or an admin can edit this game" } + } + + const [updated] = await db + .update(games) + .set({ + title: body.title ?? game.title, + developer: body.developer ?? game.developer, + publisher: body.publisher ?? game.publisher, + description: body.description ?? game.description, + headerImage: body.headerImage ?? game.headerImage, + capsuleImage: body.capsuleImage ?? game.capsuleImage, + storeUrl: body.storeUrl ?? game.storeUrl, + genres: body.genres ?? game.genres, + releaseDate: body.releaseDate ?? game.releaseDate, + updatedAt: new Date(), + }) + .where(eq(games.id, params.gameId)) + .returning() + + return { game: updated } + }, + { + params: t.Object({ gameId: t.String() }), + body: t.Object({ + title: t.Optional(t.String()), + developer: t.Optional(t.String()), + publisher: t.Optional(t.String()), + description: t.Optional(t.String()), + headerImage: t.Optional(t.String()), + capsuleImage: t.Optional(t.String()), + storeUrl: t.Optional(t.String()), + genres: t.Optional(t.Array(t.String())), + releaseDate: t.Optional(t.String()), + }), + } + ) diff --git a/lib/db/schema/games.ts b/lib/db/schema/games.ts index 5e37cfd..9113a40 100644 --- a/lib/db/schema/games.ts +++ b/lib/db/schema/games.ts @@ -62,6 +62,7 @@ export const games = pgTable( }>(), lastSync: timestamp("last_sync"), syncStatus: text("sync_status").default("pending"), + createdBy: text("created_by"), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), },