diff --git a/packages/core/migrations/0011_simple_azazel.sql b/packages/core/migrations/0011_simple_azazel.sql new file mode 100644 index 00000000..4edad2f1 --- /dev/null +++ b/packages/core/migrations/0011_simple_azazel.sql @@ -0,0 +1,89 @@ +CREATE TYPE "public"."compatibility" AS ENUM('high', 'mid', 'low', 'unknown');--> statement-breakpoint +CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer');--> statement-breakpoint +CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'superHeroArt', 'poster', 'boxArt', 'screenshot', 'background');--> statement-breakpoint +CREATE TABLE "base_games" ( + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "id" varchar(255) PRIMARY KEY NOT NULL, + "slug" varchar(255) NOT NULL, + "name" text NOT NULL, + "release_date" timestamp with time zone NOT NULL, + "size" json NOT NULL, + "description" text NOT NULL, + "primary_genre" text NOT NULL, + "controller_support" text, + "compatibility" "compatibility" DEFAULT 'unknown' NOT NULL, + "score" numeric(2, 1) NOT NULL, + CONSTRAINT "idx_base_games_slug" UNIQUE("slug") +); +--> statement-breakpoint +CREATE TABLE "categories" ( + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "slug" varchar(255) NOT NULL, + "type" "category_type" NOT NULL, + "name" text NOT NULL, + CONSTRAINT "categories_slug_type_pk" PRIMARY KEY("slug","type") +); +--> statement-breakpoint +CREATE TABLE "games" ( + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "base_game_id" varchar(255) NOT NULL, + "category_slug" varchar(255) NOT NULL, + "type" "category_type" NOT NULL, + CONSTRAINT "games_base_game_id_category_slug_type_pk" PRIMARY KEY("base_game_id","category_slug","type") +); +--> statement-breakpoint +CREATE TABLE "images" ( + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "type" "image_type" NOT NULL, + "image_hash" varchar(255) NOT NULL, + "base_game_id" varchar(255) NOT NULL, + "source_url" text NOT NULL, + "position" integer DEFAULT 0 NOT NULL, + "file_size" integer NOT NULL, + "dimensions" json NOT NULL, + "extracted_color" json NOT NULL, + CONSTRAINT "images_image_hash_type_base_game_id_position_pk" PRIMARY KEY("image_hash","type","base_game_id","position") +); +--> statement-breakpoint +CREATE TABLE "game_libraries" ( + "time_created" timestamp with time zone DEFAULT now() NOT NULL, + "time_updated" timestamp with time zone DEFAULT now() NOT NULL, + "time_deleted" timestamp with time zone, + "base_game_id" varchar(255) NOT NULL, + "owner_id" varchar(255) NOT NULL, + CONSTRAINT "game_libraries_base_game_id_owner_id_pk" PRIMARY KEY("base_game_id","owner_id") +); +--> statement-breakpoint +ALTER TABLE "steam_accounts" RENAME COLUMN "steam_id" TO "id";--> statement-breakpoint +ALTER TABLE "steam_account_credentials" DROP CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk"; +--> statement-breakpoint +ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk"; +--> statement-breakpoint +ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk"; +--> statement-breakpoint +ALTER TABLE "members" DROP CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk"; +--> statement-breakpoint +ALTER TABLE "games" ADD CONSTRAINT "games_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "images" ADD CONSTRAINT "images_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_categories_type" ON "categories" USING btree ("type");--> statement-breakpoint +CREATE INDEX "idx_games_category_slug" ON "games" USING btree ("category_slug");--> statement-breakpoint +CREATE INDEX "idx_games_category_type" ON "games" USING btree ("type");--> statement-breakpoint +CREATE INDEX "idx_images_type" ON "images" USING btree ("type");--> statement-breakpoint +CREATE INDEX "idx_images_game_id" ON "images" USING btree ("base_game_id");--> statement-breakpoint +CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_id");--> statement-breakpoint +ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint +CREATE INDEX "idx_friends_list_friend_steam_id" ON "friends_list" USING btree ("friend_steam_id"); \ No newline at end of file diff --git a/packages/core/migrations/0012_glorious_jetstream.sql b/packages/core/migrations/0012_glorious_jetstream.sql new file mode 100644 index 00000000..5e8d92b4 --- /dev/null +++ b/packages/core/migrations/0012_glorious_jetstream.sql @@ -0,0 +1,4 @@ +ALTER TABLE "games" DROP CONSTRAINT "games_categories_fkey"; +--> statement-breakpoint +ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_games_category_slug_type" ON "games" USING btree ("category_slug","type"); \ No newline at end of file diff --git a/packages/core/migrations/meta/0011_snapshot.json b/packages/core/migrations/meta/0011_snapshot.json new file mode 100644 index 00000000..3e3be95f --- /dev/null +++ b/packages/core/migrations/meta/0011_snapshot.json @@ -0,0 +1,1235 @@ +{ + "id": "c2c3c086-1b84-417e-96f3-1651a026dbaa", + "prevId": "56a4d60a-c062-47e5-a97e-625443411ad8", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.base_games": { + "name": "base_games", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "release_date": { + "name": "release_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compatibility": { + "name": "compatibility", + "type": "compatibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "score": { + "name": "score", + "type": "numeric(2, 1)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_base_games_slug": { + "name": "idx_base_games_slug", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "category_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_categories_type": { + "name": "idx_categories_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "categories_slug_type_pk": { + "name": "categories_slug_type_pk", + "columns": [ + "slug", + "type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_account_credentials": { + "name": "steam_account_credentials", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_id": { + "name": "steam_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiry": { + "name": "expiry", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "steam_account_credentials_steam_id_steam_accounts_id_fk": { + "name": "steam_account_credentials_steam_id_steam_accounts_id_fk", + "tableFrom": "steam_account_credentials", + "tableTo": "steam_accounts", + "columnsFrom": [ + "steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.friends_list": { + "name": "friends_list", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_id": { + "name": "steam_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "friend_steam_id": { + "name": "friend_steam_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_friends_list_friend_steam_id": { + "name": "idx_friends_list_friend_steam_id", + "columns": [ + { + "expression": "friend_steam_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "friends_list_steam_id_steam_accounts_id_fk": { + "name": "friends_list_steam_id_steam_accounts_id_fk", + "tableFrom": "friends_list", + "tableTo": "steam_accounts", + "columnsFrom": [ + "steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "friends_list_friend_steam_id_steam_accounts_id_fk": { + "name": "friends_list_friend_steam_id_steam_accounts_id_fk", + "tableFrom": "friends_list", + "tableTo": "steam_accounts", + "columnsFrom": [ + "friend_steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "friends_list_steam_id_friend_steam_id_pk": { + "name": "friends_list_steam_id_friend_steam_id_pk", + "columns": [ + "steam_id", + "friend_steam_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "category_slug": { + "name": "category_slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "category_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_games_category_slug": { + "name": "idx_games_category_slug", + "columns": [ + { + "expression": "category_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_games_category_type": { + "name": "idx_games_category_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "games_base_game_id_base_games_id_fk": { + "name": "games_base_game_id_base_games_id_fk", + "tableFrom": "games", + "tableTo": "base_games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "games_categories_fkey": { + "name": "games_categories_fkey", + "tableFrom": "games", + "tableTo": "categories", + "columnsFrom": [ + "category_slug", + "type" + ], + "columnsTo": [ + "slug", + "type" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "games_base_game_id_category_slug_type_pk": { + "name": "games_base_game_id_category_slug_type_pk", + "columns": [ + "base_game_id", + "category_slug", + "type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.images": { + "name": "images", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "image_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image_hash": { + "name": "image_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "base_game_id": { + "name": "base_game_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dimensions": { + "name": "dimensions", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "extracted_color": { + "name": "extracted_color", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_images_type": { + "name": "idx_images_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_images_game_id": { + "name": "idx_images_game_id", + "columns": [ + { + "expression": "base_game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "images_base_game_id_base_games_id_fk": { + "name": "images_base_game_id_base_games_id_fk", + "tableFrom": "images", + "tableTo": "base_games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "images_image_hash_type_base_game_id_position_pk": { + "name": "images_image_hash_type_base_game_id_position_pk", + "columns": [ + "image_hash", + "type", + "base_game_id", + "position" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_libraries": { + "name": "game_libraries", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_game_libraries_owner_id": { + "name": "idx_game_libraries_owner_id", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_libraries_base_game_id_base_games_id_fk": { + "name": "game_libraries_base_game_id_base_games_id_fk", + "tableFrom": "game_libraries", + "tableTo": "base_games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_libraries_owner_id_steam_accounts_id_fk": { + "name": "game_libraries_owner_id_steam_accounts_id_fk", + "tableFrom": "game_libraries", + "tableTo": "steam_accounts", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "game_libraries_base_game_id_owner_id_pk": { + "name": "game_libraries_base_game_id_owner_id_pk", + "columns": [ + "base_game_id", + "owner_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "steam_id": { + "name": "steam_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_member_steam_id": { + "name": "idx_member_steam_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "steam_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_member_user_id": { + "name": "idx_member_user_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"members\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_steam_id_steam_accounts_id_fk": { + "name": "members_steam_id_steam_accounts_id_fk", + "tableFrom": "members", + "tableTo": "steam_accounts", + "columnsFrom": [ + "steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "restrict" + } + }, + "compositePrimaryKeys": { + "members_id_team_id_pk": { + "name": "members_id_team_id_pk", + "columns": [ + "id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_accounts": { + "name": "steam_accounts", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "steam_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "real_name": { + "name": "real_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "member_since": { + "name": "member_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "profile_url": { + "name": "profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "avatar_hash": { + "name": "avatar_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "limitations": { + "name": "limitations", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "steam_accounts_user_id_users_id_fk": { + "name": "steam_accounts_user_id_users_id_fk", + "tableFrom": "steam_accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_steam_username": { + "name": "idx_steam_username", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "max_members": { + "name": "max_members", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_team_slug": { + "name": "idx_team_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_slug_steam_accounts_username_fk": { + "name": "teams_slug_steam_accounts_username_fk", + "tableFrom": "teams", + "tableTo": "steam_accounts", + "columnsFrom": [ + "slug" + ], + "columnsTo": [ + "username" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_team_invite_code": { + "name": "idx_team_invite_code", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_login": { + "name": "last_login", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "polar_customer_id": { + "name": "polar_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_user_email": { + "name": "idx_user_email", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.compatibility": { + "name": "compatibility", + "schema": "public", + "values": [ + "high", + "mid", + "low", + "unknown" + ] + }, + "public.category_type": { + "name": "category_type", + "schema": "public", + "values": [ + "tag", + "genre", + "publisher", + "developer" + ] + }, + "public.image_type": { + "name": "image_type", + "schema": "public", + "values": [ + "heroArt", + "icon", + "logo", + "superHeroArt", + "poster", + "boxArt", + "screenshot", + "background" + ] + }, + "public.member_role": { + "name": "member_role", + "schema": "public", + "values": [ + "child", + "adult" + ] + }, + "public.steam_status": { + "name": "steam_status", + "schema": "public", + "values": [ + "online", + "offline", + "dnd", + "playing" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/core/migrations/meta/0012_snapshot.json b/packages/core/migrations/meta/0012_snapshot.json new file mode 100644 index 00000000..12e6d97c --- /dev/null +++ b/packages/core/migrations/meta/0012_snapshot.json @@ -0,0 +1,1256 @@ +{ + "id": "84625b17-0a15-4d45-a32d-b0791ab07c92", + "prevId": "c2c3c086-1b84-417e-96f3-1651a026dbaa", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.base_games": { + "name": "base_games", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "release_date": { + "name": "release_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "primary_genre": { + "name": "primary_genre", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "controller_support": { + "name": "controller_support", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compatibility": { + "name": "compatibility", + "type": "compatibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "score": { + "name": "score", + "type": "numeric(2, 1)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_base_games_slug": { + "name": "idx_base_games_slug", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "category_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_categories_type": { + "name": "idx_categories_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "categories_slug_type_pk": { + "name": "categories_slug_type_pk", + "columns": [ + "slug", + "type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_account_credentials": { + "name": "steam_account_credentials", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_id": { + "name": "steam_id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiry": { + "name": "expiry", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "steam_account_credentials_steam_id_steam_accounts_id_fk": { + "name": "steam_account_credentials_steam_id_steam_accounts_id_fk", + "tableFrom": "steam_account_credentials", + "tableTo": "steam_accounts", + "columnsFrom": [ + "steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.friends_list": { + "name": "friends_list", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "steam_id": { + "name": "steam_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "friend_steam_id": { + "name": "friend_steam_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_friends_list_friend_steam_id": { + "name": "idx_friends_list_friend_steam_id", + "columns": [ + { + "expression": "friend_steam_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "friends_list_steam_id_steam_accounts_id_fk": { + "name": "friends_list_steam_id_steam_accounts_id_fk", + "tableFrom": "friends_list", + "tableTo": "steam_accounts", + "columnsFrom": [ + "steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "friends_list_friend_steam_id_steam_accounts_id_fk": { + "name": "friends_list_friend_steam_id_steam_accounts_id_fk", + "tableFrom": "friends_list", + "tableTo": "steam_accounts", + "columnsFrom": [ + "friend_steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "friends_list_steam_id_friend_steam_id_pk": { + "name": "friends_list_steam_id_friend_steam_id_pk", + "columns": [ + "steam_id", + "friend_steam_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "category_slug": { + "name": "category_slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "category_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_games_category_slug": { + "name": "idx_games_category_slug", + "columns": [ + { + "expression": "category_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_games_category_type": { + "name": "idx_games_category_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_games_category_slug_type": { + "name": "idx_games_category_slug_type", + "columns": [ + { + "expression": "category_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "games_base_game_id_base_games_id_fk": { + "name": "games_base_game_id_base_games_id_fk", + "tableFrom": "games", + "tableTo": "base_games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "games_categories_fkey": { + "name": "games_categories_fkey", + "tableFrom": "games", + "tableTo": "categories", + "columnsFrom": [ + "category_slug", + "type" + ], + "columnsTo": [ + "slug", + "type" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "games_base_game_id_category_slug_type_pk": { + "name": "games_base_game_id_category_slug_type_pk", + "columns": [ + "base_game_id", + "category_slug", + "type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.images": { + "name": "images", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "image_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "image_hash": { + "name": "image_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "base_game_id": { + "name": "base_game_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dimensions": { + "name": "dimensions", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "extracted_color": { + "name": "extracted_color", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_images_type": { + "name": "idx_images_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_images_game_id": { + "name": "idx_images_game_id", + "columns": [ + { + "expression": "base_game_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "images_base_game_id_base_games_id_fk": { + "name": "images_base_game_id_base_games_id_fk", + "tableFrom": "images", + "tableTo": "base_games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "images_image_hash_type_base_game_id_position_pk": { + "name": "images_image_hash_type_base_game_id_position_pk", + "columns": [ + "image_hash", + "type", + "base_game_id", + "position" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_libraries": { + "name": "game_libraries", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "base_game_id": { + "name": "base_game_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_game_libraries_owner_id": { + "name": "idx_game_libraries_owner_id", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "game_libraries_base_game_id_base_games_id_fk": { + "name": "game_libraries_base_game_id_base_games_id_fk", + "tableFrom": "game_libraries", + "tableTo": "base_games", + "columnsFrom": [ + "base_game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_libraries_owner_id_steam_accounts_id_fk": { + "name": "game_libraries_owner_id_steam_accounts_id_fk", + "tableFrom": "game_libraries", + "tableTo": "steam_accounts", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "game_libraries_base_game_id_owner_id_pk": { + "name": "game_libraries_base_game_id_owner_id_pk", + "columns": [ + "base_game_id", + "owner_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "steam_id": { + "name": "steam_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_member_steam_id": { + "name": "idx_member_steam_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "steam_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_member_user_id": { + "name": "idx_member_user_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"members\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_steam_id_steam_accounts_id_fk": { + "name": "members_steam_id_steam_accounts_id_fk", + "tableFrom": "members", + "tableTo": "steam_accounts", + "columnsFrom": [ + "steam_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "restrict" + } + }, + "compositePrimaryKeys": { + "members_id_team_id_pk": { + "name": "members_id_team_id_pk", + "columns": [ + "id", + "team_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.steam_accounts": { + "name": "steam_accounts", + "schema": "", + "columns": { + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "char(30)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "steam_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "real_name": { + "name": "real_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "member_since": { + "name": "member_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "profile_url": { + "name": "profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "avatar_hash": { + "name": "avatar_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "limitations": { + "name": "limitations", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "steam_accounts_user_id_users_id_fk": { + "name": "steam_accounts_user_id_users_id_fk", + "tableFrom": "steam_accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_steam_username": { + "name": "idx_steam_username", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "char(30)", + "primaryKey": false, + "notNull": true + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "max_members": { + "name": "max_members", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_team_slug": { + "name": "idx_team_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_slug_steam_accounts_username_fk": { + "name": "teams_slug_steam_accounts_username_fk", + "tableFrom": "teams", + "tableTo": "steam_accounts", + "columnsFrom": [ + "slug" + ], + "columnsTo": [ + "username" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_team_invite_code": { + "name": "idx_team_invite_code", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(30)", + "primaryKey": true, + "notNull": true + }, + "time_created": { + "name": "time_created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_login": { + "name": "last_login", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "polar_customer_id": { + "name": "polar_customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "idx_user_email": { + "name": "idx_user_email", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.compatibility": { + "name": "compatibility", + "schema": "public", + "values": [ + "high", + "mid", + "low", + "unknown" + ] + }, + "public.category_type": { + "name": "category_type", + "schema": "public", + "values": [ + "tag", + "genre", + "publisher", + "developer" + ] + }, + "public.image_type": { + "name": "image_type", + "schema": "public", + "values": [ + "heroArt", + "icon", + "logo", + "superHeroArt", + "poster", + "boxArt", + "screenshot", + "background" + ] + }, + "public.member_role": { + "name": "member_role", + "schema": "public", + "values": [ + "child", + "adult" + ] + }, + "public.steam_status": { + "name": "steam_status", + "schema": "public", + "values": [ + "online", + "offline", + "dnd", + "playing" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/core/migrations/meta/_journal.json b/packages/core/migrations/meta/_journal.json index 6d238791..859fbe54 100644 --- a/packages/core/migrations/meta/_journal.json +++ b/packages/core/migrations/meta/_journal.json @@ -78,6 +78,20 @@ "when": 1746726715456, "tag": "0010_certain_dust", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1746904821461, + "tag": "0011_simple_azazel", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1746905730079, + "tag": "0012_glorious_jetstream", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/core/src/examples.ts b/packages/core/src/examples.ts index 4d159ab6..0d96a50a 100644 --- a/packages/core/src/examples.ts +++ b/packages/core/src/examples.ts @@ -207,17 +207,60 @@ export namespace Examples { ], } - export const Game = { - ...BaseGame, - ...Categories + export const CommonImg = [ + { + hash: "db880dc2f0187bfe0c5d3c44a06d1002351eb3107970a83bf5667ffd3b369acd", + averageColor: { + hex: "#352c36", + isDark: true + }, + dimensions: { + width: 3840, + height: 2160 + }, + fileSize: 976004 + }, + { + hash: "99f603e41dd3efde21a145fd00c9f107025c09433c084a5e5005bc2ac30e46ea", + averageColor: { + hex: "#596774", + isDark: true + }, + dimensions: { + width: 2560, + height: 1440 + }, + fileSize: 895134 + }, + { + hash: "2c4193c19160392be01d08e6957ed682649117742c5abaa8c469e7408382572f", + averageColor: { + hex: "#444b5b", + isDark: true + }, + dimensions: { + width: 2560, + height: 1440 + }, + fileSize: 738701 + } + ] + + // type: "screenshots" as const, // or boxart(square), poster(vertical), superheroart(background), heroart(horizontal), logo, icon + export const Images = { + screenshots: CommonImg, + boxArts: CommonImg, + posters: CommonImg, + heroArts: CommonImg, + superHeroArts: CommonImg, + backgrounds: CommonImg, + logos: CommonImg, + icons: CommonImg, } - // export const image = { - // type: "screenshot" as const, // or square, vertical, horizontal, movie - // hash: "3a5e805fd4c1e04e26a97af0b9c6fab2dee91a19", - // gameID: Game.id, - // extractedColors: [{}] - // } - - + export const Game = { + ...BaseGame, + ...Categories, + ...Images + } } \ No newline at end of file diff --git a/packages/core/src/game/game.sql.ts b/packages/core/src/game/game.sql.ts index acefd862..95a7f67b 100644 --- a/packages/core/src/game/game.sql.ts +++ b/packages/core/src/game/game.sql.ts @@ -1,7 +1,7 @@ import { timestamps } from "../drizzle/types"; import { baseGamesTable } from "../base-game/base-game.sql"; -import { categoriesTable } from "../categories/categories.sql"; -import { index, pgTable, primaryKey, varchar } from "drizzle-orm/pg-core"; +import { categoriesTable, CategoryTypeEnum } from "../categories/categories.sql"; +import { foreignKey, index, pgTable, primaryKey, varchar } from "drizzle-orm/pg-core"; export const gamesTable = pgTable( 'games', @@ -13,21 +13,23 @@ export const gamesTable = pgTable( { onDelete: "cascade" } ), categorySlug: varchar('category_slug', { length: 255 }) - .notNull() - .references(() => categoriesTable.slug, - { onDelete: "cascade" } - ), - categoryType: varchar('category_type', { length: 255 }) - .notNull() - .references(() => categoriesTable.type, - { onDelete: "cascade" } - ), + .notNull(), + categoryType: CategoryTypeEnum("type").notNull() }, (table) => [ primaryKey({ columns: [table.baseGameID, table.categorySlug, table.categoryType] }), + foreignKey({ + name: "games_categories_fkey", + columns: [table.categorySlug, table.categoryType], + foreignColumns: [categoriesTable.slug, categoriesTable.type], + }).onDelete("cascade"), index("idx_games_category_slug").on(table.categorySlug), index("idx_games_category_type").on(table.categoryType), + index("idx_games_category_slug_type").on( + table.categorySlug, + table.categoryType + ) ] ); \ No newline at end of file diff --git a/packages/core/src/game/index.ts b/packages/core/src/game/index.ts index c4bb1281..8531700b 100644 --- a/packages/core/src/game/index.ts +++ b/packages/core/src/game/index.ts @@ -1,11 +1,13 @@ import { z } from "zod"; import { fn } from "../utils"; +import { Images } from "../images"; import { Examples } from "../examples"; import { BaseGame } from "../base-game"; import { gamesTable } from "./game.sql"; import { Categories } from "../categories"; import { eq, and, isNull } from "drizzle-orm"; import { createSelectSchema } from "drizzle-zod"; +import { imagesTable } from "../images/images.sql"; import { groupBy, map, pipe, uniqueBy, values } from "remeda"; import { baseGamesTable } from "../base-game/base-game.sql"; import { categoriesTable } from "../categories/categories.sql"; @@ -13,7 +15,7 @@ import { createTransaction, useTransaction } from "../drizzle/transaction"; export namespace Game { export const Info = z - .intersection(BaseGame.Info, Categories.Info) + .intersection(BaseGame.Info, Categories.Info, Images.Info) .openapi({ ref: "Game", description: "Detailed information about a game available in the Nestri library, including technical specifications, categories and metadata", @@ -65,6 +67,7 @@ export namespace Game { .select({ games: baseGamesTable, categories: categoriesTable, + images: imagesTable }) .from(gamesTable) .innerJoin(baseGamesTable, @@ -76,6 +79,12 @@ export namespace Game { eq(categoriesTable.type, gamesTable.categoryType), ) ) + .leftJoin(imagesTable, + and( + eq(imagesTable.baseGameID, gamesTable.baseGameID), + isNull(imagesTable.timeDeleted), + ) + ) .where( and( eq(gamesTable.baseGameID, gameID), @@ -88,7 +97,7 @@ export namespace Game { ) export function serialize( - input: { games: typeof baseGamesTable.$inferSelect; categories: typeof categoriesTable.$inferSelect | null }[], + input: { games: typeof baseGamesTable.$inferSelect; categories: typeof categoriesTable.$inferSelect | null; images: typeof imagesTable.$inferSelect | null }[], ): z.infer[] { return pipe( input, @@ -100,10 +109,16 @@ export namespace Game { group.map(r => r.categories).filter((c): c is typeof categoriesTable.$inferSelect => Boolean(c)), (c) => `${c.slug}:${c.type}` ) + const imgs = uniqueBy( + group.map(r => r.images).filter((c): c is typeof imagesTable.$inferSelect => Boolean(c)), + (c) => `${c.type}:${c.imageHash}:${c.position}` + ) const byType = Categories.serialize(cats) + const byImg = Images.serialize(imgs) return { ...game, ...byType, + ...byImg } }) ) diff --git a/packages/core/src/images/images.sql.ts b/packages/core/src/images/images.sql.ts new file mode 100644 index 00000000..b141308d --- /dev/null +++ b/packages/core/src/images/images.sql.ts @@ -0,0 +1,46 @@ +import { timestamps } from "../drizzle/types"; +import { baseGamesTable } from "../base-game/base-game.sql"; +import { index, integer, json, pgEnum, pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core"; +import { z } from "zod"; + +export const ImageTypeEnum = pgEnum("image_type", ["heroArt", "icon", "logo", "superHeroArt", "poster", "boxArt", "screenshot","background"]) + +export const ImageDimensions = z.object({ + width: z.number().int(), + height: z.number().int(), +}) + +export const ImageColor = z.object({ + hex: z.string(), + isDark: z.boolean() +}) + +export type ImageColor = z.infer; +export type ImageDimensions = z.infer; + +export const imagesTable = pgTable( + "images", + { + ...timestamps, + type: ImageTypeEnum("type").notNull(), + imageHash: varchar("image_hash", { length: 255 }) + .notNull(), + baseGameID: varchar("base_game_id", { length: 255 }) + .notNull() + .references(() => baseGamesTable.id, { + onDelete: "cascade" + }), + sourceUrl: text("source_url").notNull(), + position: integer("position").notNull().default(0), + fileSize: integer("file_size").notNull(), + dimensions: json("dimensions").$type().notNull(), + extractedColor: json("extracted_color").$type().notNull(), + }, + (table) => [ + primaryKey({ + columns: [table.imageHash, table.type, table.baseGameID, table.position] + }), + index("idx_images_type").on(table.type), + index("idx_images_game_id").on(table.baseGameID), + ] +) \ No newline at end of file diff --git a/packages/core/src/images/index.ts b/packages/core/src/images/index.ts new file mode 100644 index 00000000..52d6e45a --- /dev/null +++ b/packages/core/src/images/index.ts @@ -0,0 +1,119 @@ +import { z } from "zod"; +import { fn } from "../utils"; +import { Examples } from "../examples"; +import { createSelectSchema } from "drizzle-zod"; +import { createTransaction } from "../drizzle/transaction"; +import { ImageColor, ImageDimensions, imagesTable } from "./images.sql"; + +export namespace Images { + const Image = z.object({ + hash: z.string().openapi({ + description: "A unique cryptographic hash identifier for the image, used for deduplication and URL generation", + example: Examples.CommonImg[0].hash + }), + averageColor: ImageColor.openapi({ + description: "The calculated dominant color of the image with light/dark classification, used for UI theming", + example: Examples.CommonImg[0].averageColor + }), + dimensions: ImageDimensions.openapi({ + description: "The width and height dimensions of the image in pixels", + example: Examples.CommonImg[0].dimensions + }), + fileSize: z.number().int().openapi({ + description: "The size of the image file in bytes, used for storage and bandwidth calculations", + example: Examples.CommonImg[0].fileSize + }) + }) + + export const Info = z.object({ + screenshots: Image.array().openapi({ + description: "In-game captured images showing actual gameplay, user interface, and key moments", + example: Examples.Images.screenshots + }), + boxArts: Image.array().openapi({ + description: "Square 1:1 aspect ratio artwork, typically used for store listings and thumbnails", + example: Examples.Images.boxArts + }), + posters: Image.array().openapi({ + description: "Vertical 2:3 aspect ratio promotional artwork, similar to movie posters", + example: Examples.Images.posters + }), + heroArts: Image.array().openapi({ + description: "Horizontal promotional artwork optimized for header displays and banners", + example: Examples.Images.heroArts + }), + superHeroArts: Image.array().openapi({ + description: "High-resolution, wide-format artwork designed for featured content and main entries", + example: Examples.Images.superHeroArts + }), + backgrounds: Image.array().openapi({ + description: "Full-width background images optimized for page layouts and decorative purposes", + example: Examples.Images.backgrounds + }), + logos: Image.array().openapi({ + description: "Official game logo artwork, typically with transparent backgrounds for flexible placement", + example: Examples.Images.logos + }), + icons: Image.array().openapi({ + description: "Small-format identifiers used for application shortcuts and compact displays", + example: Examples.Images.icons + }), + }).openapi({ + ref: "Images", + description: "Complete collection of game-related visual assets, including promotional materials, UI elements, and store assets", + example: Examples.Images + }) + + export type Info = z.infer + + export const InputInfo = createSelectSchema(imagesTable) + .omit({ timeCreated: true, timeDeleted: true, timeUpdated: true }) + + export const create = fn( + InputInfo, + (input) => + createTransaction(async (tx) => + tx + .insert(imagesTable) + .values(input) + .onConflictDoUpdate({ + target: [imagesTable.imageHash, imagesTable.type, imagesTable.baseGameID, imagesTable.position], + set: { timeDeleted: null } + }) + ) + ) + + export function serialize( + input: typeof imagesTable.$inferSelect[], + ): z.infer { + return input + .sort((a, b) => { + if (a.type === b.type) { + return a.position - b.position; + } + return a.type.localeCompare(b.type); + }) + .reduce>((acc, img) => { + const key = `${img.type}s` as `${typeof img.type}s` + if (Array.isArray(acc[key])) { + acc[key]!.push({ + hash: img.imageHash, + averageColor: img.extractedColor, + dimensions: img.dimensions, + fileSize: img.fileSize + }) + } + return acc + }, { + screenshots: [], + boxArts: [], + superHeroArts: [], + heroArts: [], + posters: [], + backgrounds: [], + icons: [], + logos: [], + }) + } + +} \ No newline at end of file diff --git a/packages/core/src/library/index.ts b/packages/core/src/library/index.ts index 41d9e037..25408838 100644 --- a/packages/core/src/library/index.ts +++ b/packages/core/src/library/index.ts @@ -1,14 +1,15 @@ import { z } from "zod"; import { fn } from "../utils"; import { Game } from "../game"; +import { Actor } from "../actor"; import { gamesTable } from "../game/game.sql"; import { createSelectSchema } from "drizzle-zod"; import { steamLibraryTable } from "./library.sql"; -import { and, eq, inArray, isNull, sql } from "drizzle-orm"; +import { imagesTable } from "../images/images.sql"; +import { and, eq, isNull, sql } from "drizzle-orm"; import { baseGamesTable } from "../base-game/base-game.sql"; import { categoriesTable } from "../categories/categories.sql"; import { createTransaction, useTransaction } from "../drizzle/transaction"; -import { Actor } from "../actor"; export namespace Library { export const Info = createSelectSchema(steamLibraryTable) @@ -26,7 +27,7 @@ export namespace Library { .from(steamLibraryTable) .where( and( - eq(steamLibraryTable.gameID, input.gameID), + eq(steamLibraryTable.baseGameID, input.baseGameID), eq(steamLibraryTable.ownerID, input.ownerID), isNull(steamLibraryTable.timeDeleted) ) @@ -37,12 +38,9 @@ export namespace Library { await tx .insert(steamLibraryTable) - .values({ - ownerID: input.ownerID, - gameID: input.gameID - }) + .values(input) .onConflictDoUpdate({ - target: [steamLibraryTable.ownerID, steamLibraryTable.gameID], + target: [steamLibraryTable.ownerID, steamLibraryTable.baseGameID], set: { timeDeleted: null } }) @@ -59,7 +57,7 @@ export namespace Library { .where( and( eq(steamLibraryTable.ownerID, input.ownerID), - eq(steamLibraryTable.gameID, input.gameID), + eq(steamLibraryTable.baseGameID, input.baseGameID), ) ) ) @@ -71,6 +69,7 @@ export namespace Library { .select({ games: baseGamesTable, categories: categoriesTable, + images: imagesTable }) .from(steamLibraryTable) .where( @@ -81,7 +80,7 @@ export namespace Library { ) .innerJoin( baseGamesTable, - eq(baseGamesTable.id, steamLibraryTable.gameID), + eq(baseGamesTable.id, steamLibraryTable.baseGameID), ) .leftJoin( gamesTable, @@ -94,6 +93,20 @@ export namespace Library { eq(categoriesTable.type, gamesTable.categoryType), ) ) + // Joining imagesTable 1-N with gamesTable multiplies rows; the subsequent Game.serialize has to uniqueBy to undo this. + // For large libraries with many screenshots the Cartesian effect can significantly bloat the result and network payload. + // One option is to aggregate the images in SQL before joining to keep exactly one row per game: + // .leftJoin( + // sql`(SELECT * FROM images WHERE base_game_id = ${gamesTable.baseGameID} AND time_deleted IS NULL ORDER BY type, position)`.as("images"), + // sql`TRUE` + // ) + .leftJoin( + imagesTable, + and( + eq(imagesTable.baseGameID, gamesTable.baseGameID), + isNull(imagesTable.timeDeleted), + ) + ) .execute() .then(rows => Game.serialize(rows)) ) diff --git a/packages/core/src/library/library.sql.ts b/packages/core/src/library/library.sql.ts index 83842676..7eeb9c71 100644 --- a/packages/core/src/library/library.sql.ts +++ b/packages/core/src/library/library.sql.ts @@ -8,7 +8,7 @@ export const steamLibraryTable = pgTable( "game_libraries", { ...timestamps, - gameID: varchar("game_id", { length: 255 }) + baseGameID: varchar("base_game_id", { length: 255 }) .notNull() .references(() => baseGamesTable.id, { onDelete: "cascade" @@ -21,7 +21,7 @@ export const steamLibraryTable = pgTable( }, (table) => [ primaryKey({ - columns: [table.gameID, table.ownerID] + columns: [table.baseGameID, table.ownerID] }), index("idx_game_libraries_owner_id").on(table.ownerID), ], diff --git a/packages/zero/schema.ts b/packages/zero/schema.ts index 4f49e661..2b4fee65 100644 --- a/packages/zero/schema.ts +++ b/packages/zero/schema.ts @@ -1,5 +1,6 @@ import { Size } from "@nestri/core/src/base-game/base-game.sql"; import { type Limitations } from "@nestri/core/src/steam/steam.sql"; +import { ImageColor, ImageDimensions } from "@nestri/core/src/images/images.sql"; import { json, table, @@ -82,9 +83,10 @@ const games = table("games") .columns({ base_game_id: string(), category_slug: string(), + type: enumeration<"tag" | "genre" | "publisher" | "developer">(), ...timestamps }) - .primaryKey("category_slug", "base_game_id") + .primaryKey("category_slug", "base_game_id", "type") const base_games = table("base_games") .columns({ @@ -96,7 +98,7 @@ const base_games = table("base_games") description: string(), primary_genre: string(), controller_support: string().optional(), - compatibility: enumeration<"high" | "mid" | "low">(), + compatibility: enumeration<"high" | "mid" | "low" | "unknown">(), score: number(), ...timestamps }) @@ -109,17 +111,29 @@ const categories = table("categories") name: string(), ...timestamps }) - .primaryKey("slug") + .primaryKey("slug", "type") const game_libraries = table("game_libraries") .columns({ - game_id: string(), - owner_id: string() - }) + base_game_id: string(), + owner_id: string(), + ...timestamps + }).primaryKey("base_game_id", "owner_id") + +const images = table("images") + .columns({ + image_hash: string(), + base_game_id: string(), + type: enumeration<"heroArt" | "icon" | "logo" | "superHeroArt" | "poster" | "boxArt" | "screenshot" | "background">(), + position: number(), + dimensions: json(), + extracted_color: json(), + ...timestamps + }).primaryKey("image_hash", "type", "base_game_id", "position") // Schema and Relationships export const schema = createSchema({ - tables: [users, steam_accounts, teams, members, friends_list, categories, base_games, games, game_libraries], + tables: [users, steam_accounts, teams, members, friends_list, categories, base_games, games, game_libraries, images], relationships: [ relationships(steam_accounts, (r) => ({ user: r.one({ @@ -215,22 +229,37 @@ export const schema = createSchema({ libraries: r.many({ sourceField: ["id"], destSchema: game_libraries, - destField: ["game_id"] + destField: ["base_game_id"] + }), + images: r.many({ + sourceField: ["id"], + destSchema: images, + destField: ["base_game_id"] }) })), relationships(categories, (r) => ({ - games: r.many({ + games_slug: r.many({ sourceField: ["slug"], destSchema: games, destField: ["category_slug"] + }), + games_type: r.many({ + sourceField: ["type"], + destSchema: games, + destField: ["type"] }) })), relationships(games, (r) => ({ - category: r.one({ + category_slug: r.one({ sourceField: ["category_slug"], destSchema: categories, destField: ["slug"], }), + category_type: r.one({ + sourceField: ["type"], + destSchema: categories, + destField: ["type"], + }), base_game: r.one({ sourceField: ["base_game_id"], destSchema: base_games, @@ -239,7 +268,7 @@ export const schema = createSchema({ })), relationships(game_libraries, (r) => ({ base_game: r.one({ - sourceField: ["game_id"], + sourceField: ["base_game_id"], destSchema: base_games, destField: ["id"], }), @@ -249,6 +278,13 @@ export const schema = createSchema({ destField: ["id"], }), })), + relationships(images, (r) => ({ + base_game: r.one({ + sourceField: ["base_game_id"], + destSchema: base_games, + destField: ["id"], + }), + })), ], }); @@ -307,6 +343,17 @@ export const permissions = definePermissions(schema, () => { ] }, }, + game_libraries: { + row: { + select: [ + (auth: Auth, q: ExpressionBuilder) => q.exists("owner", (u) => u.where("user_id", auth.sub)), + //allow team members to see the other members' libraries + (auth: Auth, q: ExpressionBuilder) => q.exists("owner", (u) => u.related("memberEntries", (f) => f.where("user_id", auth.sub))), + //allow friends to see their friends libraries + (auth: Auth, q: ExpressionBuilder) => q.exists("owner", (u) => u.related("friends", (f) => f.related("friend", (s) => s.where("user_id", auth.sub)))), + ] + } + }, //Games are publicly viewable games: { row: { @@ -323,14 +370,10 @@ export const permissions = definePermissions(schema, () => { select: ANYONE_CAN } }, - game_libraries: { + images: { row: { - select: [ - (auth: Auth, q: ExpressionBuilder) => q.exists("owner", (u) => u.where("user_id", auth.sub)), - (auth: Auth, q: ExpressionBuilder) => q.exists("owner", (u) => u.related("memberEntries", (f) => f.where("user_id", auth.sub))), - (auth: Auth, q: ExpressionBuilder) => q.exists("owner", (u) => u.related("friends", (f) => f.related("friend", (s) => s.where("user_id", auth.sub)))), - ] + select: ANYONE_CAN } - } + }, }; }); \ No newline at end of file