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(),
},