mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-13 17:25:36 +02:00
fix: Move more directories
This commit is contained in:
44
cloud/packages/core/src/base-game/base-game.sql.ts
Normal file
44
cloud/packages/core/src/base-game/base-game.sql.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { z } from "zod";
|
||||
import { timestamps, utc } from "../drizzle/types";
|
||||
import { json, numeric, pgEnum, pgTable, text, unique, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
export const CompatibilityEnum = pgEnum("compatibility", ["high", "mid", "low", "unknown"])
|
||||
export const ControllerEnum = pgEnum("controller_support", ["full", "partial", "unknown"])
|
||||
|
||||
export const Size =
|
||||
z.object({
|
||||
downloadSize: z.number().positive().int(),
|
||||
sizeOnDisk: z.number().positive().int()
|
||||
});
|
||||
|
||||
export const Links = z.string().array();
|
||||
|
||||
export type Size = z.infer<typeof Size>;
|
||||
export type Links = z.infer<typeof Links>;
|
||||
|
||||
export const baseGamesTable = pgTable(
|
||||
"base_games",
|
||||
{
|
||||
...timestamps,
|
||||
id: varchar("id", { length: 255 })
|
||||
.primaryKey()
|
||||
.notNull(),
|
||||
links: json("links").$type<Links>(),
|
||||
slug: varchar("slug", { length: 255 })
|
||||
.notNull(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
releaseDate: utc("release_date").notNull(),
|
||||
size: json("size").$type<Size>().notNull(),
|
||||
primaryGenre: text("primary_genre"),
|
||||
controllerSupport: ControllerEnum("controller_support").notNull(),
|
||||
compatibility: CompatibilityEnum("compatibility").notNull().default("unknown"),
|
||||
// Score ranges from 0.0 to 5.0
|
||||
score: numeric("score", { precision: 2, scale: 1 })
|
||||
.$type<number>()
|
||||
.notNull()
|
||||
},
|
||||
(table) => [
|
||||
unique("idx_base_games_slug").on(table.slug),
|
||||
]
|
||||
)
|
||||
162
cloud/packages/core/src/base-game/index.ts
Normal file
162
cloud/packages/core/src/base-game/index.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { z } from "zod";
|
||||
import { fn } from "../utils";
|
||||
import { Common } from "../common";
|
||||
import { Examples } from "../examples";
|
||||
import { createEvent } from "../event";
|
||||
import { eq, isNull, and } from "drizzle-orm";
|
||||
import { ImageTypeEnum } from "../images/images.sql";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
import { CompatibilityEnum, baseGamesTable, Size, ControllerEnum, Links } from "./base-game.sql";
|
||||
|
||||
export namespace BaseGame {
|
||||
export const Info = z.object({
|
||||
id: z.string().openapi({
|
||||
description: Common.IdDescription,
|
||||
example: Examples.BaseGame.id
|
||||
}),
|
||||
slug: z.string().openapi({
|
||||
description: "A URL-friendly unique identifier for the game, used in web addresses and API endpoints",
|
||||
example: Examples.BaseGame.slug
|
||||
}),
|
||||
name: z.string().openapi({
|
||||
description: "The official title of the game as listed on Steam",
|
||||
example: Examples.BaseGame.name
|
||||
}),
|
||||
size: Size.openapi({
|
||||
description: "Storage requirements in bytes: downloadSize represents the compressed download, and sizeOnDisk represents the installed size",
|
||||
example: Examples.BaseGame.size
|
||||
}),
|
||||
releaseDate: z.date().openapi({
|
||||
description: "The initial public release date of the game on Steam",
|
||||
example: Examples.BaseGame.releaseDate
|
||||
}),
|
||||
description: z.string().nullable().openapi({
|
||||
description: "A comprehensive overview of the game, including its features, storyline, and gameplay elements",
|
||||
example: Examples.BaseGame.description
|
||||
}),
|
||||
score: z.number().openapi({
|
||||
description: "The aggregate user review score on Steam, represented as a percentage of positive reviews",
|
||||
example: Examples.BaseGame.score
|
||||
}),
|
||||
links: Links
|
||||
.nullable()
|
||||
.openapi({
|
||||
description: "The social links of this game",
|
||||
example: Examples.BaseGame.links
|
||||
}),
|
||||
primaryGenre: z.string().nullable().openapi({
|
||||
description: "The main category or genre that best represents the game's content and gameplay style",
|
||||
example: Examples.BaseGame.primaryGenre
|
||||
}),
|
||||
controllerSupport: z.enum(ControllerEnum.enumValues).openapi({
|
||||
description: "Indicates the level of gamepad/controller compatibility: 'Full', 'Partial', or 'Unkown' for no support",
|
||||
example: Examples.BaseGame.controllerSupport
|
||||
}),
|
||||
compatibility: z.enum(CompatibilityEnum.enumValues).openapi({
|
||||
description: "Steam Deck/Proton compatibility rating indicating how well the game runs on Linux systems",
|
||||
example: Examples.BaseGame.compatibility
|
||||
}),
|
||||
}).openapi({
|
||||
ref: "BaseGame",
|
||||
description: "Detailed information about a game available in the Nestri library, including technical specifications and metadata",
|
||||
example: Examples.BaseGame
|
||||
})
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const Events = {
|
||||
New: createEvent(
|
||||
"new_image.save",
|
||||
z.object({
|
||||
appID: Info.shape.id,
|
||||
url: z.string().url(),
|
||||
type: z.enum(ImageTypeEnum.enumValues)
|
||||
}),
|
||||
),
|
||||
NewBoxArt: createEvent(
|
||||
"new_box_art_image.save",
|
||||
z.object({
|
||||
appID: Info.shape.id,
|
||||
logoUrl: z.string().url(),
|
||||
backgroundUrl: z.string().url(),
|
||||
}),
|
||||
),
|
||||
NewHeroArt: createEvent(
|
||||
"new_hero_art_image.save",
|
||||
z.object({
|
||||
appID: Info.shape.id,
|
||||
backdropUrl: z.string().url(),
|
||||
screenshots: z.string().url().array(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
export const create = fn(
|
||||
Info,
|
||||
(input) =>
|
||||
createTransaction(async (tx) => {
|
||||
const result = await tx
|
||||
.select()
|
||||
.from(baseGamesTable)
|
||||
.where(
|
||||
and(
|
||||
eq(baseGamesTable.id, input.id),
|
||||
isNull(baseGamesTable.timeDeleted)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.execute()
|
||||
.then(rows => rows.at(0))
|
||||
|
||||
if (result) return result.id
|
||||
|
||||
await tx
|
||||
.insert(baseGamesTable)
|
||||
.values(input)
|
||||
.onConflictDoUpdate({
|
||||
target: baseGamesTable.id,
|
||||
set: {
|
||||
timeDeleted: null
|
||||
}
|
||||
})
|
||||
|
||||
return input.id
|
||||
})
|
||||
)
|
||||
|
||||
export const fromID = fn(
|
||||
Info.shape.id,
|
||||
(id) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(baseGamesTable)
|
||||
.where(
|
||||
and(
|
||||
eq(baseGamesTable.id, id),
|
||||
isNull(baseGamesTable.timeDeleted)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.then(rows => rows.map(serialize).at(0))
|
||||
)
|
||||
)
|
||||
|
||||
export function serialize(
|
||||
input: typeof baseGamesTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
name: input.name,
|
||||
slug: input.slug,
|
||||
size: input.size,
|
||||
links: input.links,
|
||||
score: input.score,
|
||||
description: input.description,
|
||||
releaseDate: input.releaseDate,
|
||||
primaryGenre: input.primaryGenre,
|
||||
compatibility: input.compatibility,
|
||||
controllerSupport: input.controllerSupport,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user