mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-23 19:18:18 +03:00
⭐feat: Implement Game Image Support with Metadata & Schema Updates (#277)
## Description <!-- Briefly describe the purpose and scope of your changes --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced support for associating rich image metadata (color, dimensions, file size) with games, organized by categories like screenshots, box art, posters, hero art, backgrounds, logos, and icons. - Game and library listings now include related image collections for enhanced browsing and detail views. - **Improvements** - Updated game library management to use a consistent base game identifier, improving data consistency and reliability. - Enhanced data schemas and access permissions to allow public viewing of game images and refined access control for game libraries. - Added comprehensive database schema updates for games, categories, images, and libraries to support new features and ensure data integrity. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -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
|
||||
)
|
||||
]
|
||||
);
|
||||
@@ -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<typeof Info>[] {
|
||||
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
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user