feat: replace Category table with string fields in Equipment

- Removed Category table from schema.prisma
- Updated Equipment table to use string `category` and `subCategory`
- Created `src/lib/categories.ts` with hardcoded categories
- Updated `ExtractionService` to use hardcoded categories and "Others" fallback
- Updated `Extract` API to stream `asset_refined` events
- Updated Frontend to handle new data structure and SSE events
- Updated tests to reflect changes
This commit is contained in:
google-labs-jules[bot] committed 2026-02-08 09:10:22 +00:00
1 parent 4a814e117c
commit 44291666e7
28 files changed
+1753 -6209

No files matched your search

@@ -0,0 +1,33 @@
/*
Warnings:
- You are about to drop the `categories` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the column `category_id` on the `equipments` table. All the data in the column will be lost.
- You are about to drop the column `sub_category_id` on the `equipments` table. All the data in the column will be lost.
- Added the required column `category` to the `equipments` table without a default value. This is not possible if the table is not empty.
*/
-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "categories";
PRAGMA foreign_keys=on;
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_equipments" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"category" TEXT NOT NULL,
"sub_category" TEXT,
"image_id" TEXT NOT NULL,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL,
CONSTRAINT "equipments_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "assets" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_equipments" ("created_at", "description", "id", "image_id", "name", "updated_at") SELECT "created_at", "description", "id", "image_id", "name", "updated_at" FROM "equipments";
DROP TABLE "equipments";
ALTER TABLE "new_equipments" RENAME TO "equipments";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
+10 -28
View File
@@ -42,37 +42,19 @@ model Character {
@@map("characters")
}
model Category {
id String @id
name String
parentId String? @map("parent_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
parent Category? @relation("CategoryToSubCategory", fields: [parentId], references: [id])
subCategories Category[] @relation("CategoryToSubCategory")
equipmentsAsMain Equipment[] @relation("CategoryMain")
equipmentsAsSub Equipment[] @relation("CategorySub")
@@map("categories")
}
model Equipment {
id String @id
name String
description String
categoryId String @map("category_id")
subCategoryId String? @map("sub_category_id")
imageId String @map("image_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
id String @id
name String
description String
category String
subCategory String? @map("sub_category")
imageId String @map("image_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
category Category @relation("CategoryMain", fields: [categoryId], references: [id])
subCategory Category? @relation("CategorySub", fields: [subCategoryId], references: [id])
image Asset @relation(fields: [imageId], references: [id])
generations GenerationEquipment[]
image Asset @relation(fields: [imageId], references: [id])
generations GenerationEquipment[]
@@map("equipments")
}
+2 -2
View File
@@ -17,7 +17,7 @@ describe('Equipment API', () => {
it('GET / should list equipments with filters', async () => {
mockEquipmentService.listEquipments.mockResolvedValue({ items: [], total: 0 })
const req = new Request('http://localhost/?categoryId=cat1', {
const req = new Request('http://localhost/?category=Headwear', {
method: 'GET',
})
@@ -25,7 +25,7 @@ describe('Equipment API', () => {
expect(res.status).toBe(200)
expect(mockEquipmentService.listEquipments).toHaveBeenCalledWith(
{ page: 1, limit: 20 },
{ categoryId: 'cat1', subCategoryId: undefined },
{ category: 'Headwear', subCategory: undefined },
)
})
})
+4 -7
View File
@@ -8,17 +8,14 @@ const app = new Hono()
const listSchema = z.object({
page: z.coerce.number().optional().default(1),
limit: z.coerce.number().optional().default(20),
categoryId: z.string().optional(),
subCategoryId: z.string().optional(),
category: z.string().optional(),
subCategory: z.string().optional(),
})
const route = app.get('/', zValidator('query', listSchema), async (c) => {
const { page, limit, categoryId, subCategoryId } = c.req.valid('query')
const { page, limit, category, subCategory } = c.req.valid('query')
const result = await equipmentService.listEquipments(
{ page, limit },
{ categoryId, subCategoryId },
)
const result = await equipmentService.listEquipments({ page, limit }, { category, subCategory })
return c.json(result)
})
+15 -9
View File
@@ -5,7 +5,6 @@ import { z } from 'zod'
import { extractionService } from '../service/extraction'
import { fileService } from '../service/file'
import { equipmentService } from '../service/equipment'
import { categoryService } from '../service/category'
import { assetService } from '../service/asset'
const app = new Hono()
@@ -66,7 +65,7 @@ const route = app.post('/', zValidator('form', extractSchema), async (c) => {
let boxes
try {
boxes = await extractionService.detectBoundingBoxes(sheetBase64)
boxes = await extractionService.detectBoundingBoxes(sheetBase64, analysis.assets)
} catch (err: unknown) {
throw new Error(`Detection failed: ${err instanceof Error ? err.message : String(err)}`, {
cause: err,
@@ -89,7 +88,8 @@ const route = app.post('/', zValidator('form', extractSchema), async (c) => {
const originalMeta = analysis.assets.find((a) => a.item_name === crop.name) || {
item_name: crop.name,
description: 'Extracted asset',
category: 'Uncategorized',
category: 'Others',
sub_category: 'Others',
background_color: 'unknown',
}
@@ -106,20 +106,26 @@ const route = app.post('/', zValidator('form', extractSchema), async (c) => {
path: savedFile.filename,
})
// Find or create category
const categoryId = await categoryService.findOrCreate(originalMeta.category)
// Create Equipment record linked to Asset and Category
// Create Equipment record linked to Asset and using string categories
const equipment = await equipmentService.createEquipment({
name: originalMeta.item_name,
description: originalMeta.description,
imageId: asset.id,
categoryId: categoryId,
category: originalMeta.category,
subCategory: originalMeta.sub_category,
})
refinedAssets.push({
const refinedAsset = {
...equipment,
imageUrl: `/files/${savedFile.filename}`, // Frontend needs URL
}
refinedAssets.push(refinedAsset)
// Stream event for progressive display
await stream.writeSSE({
event: 'asset_refined',
data: JSON.stringify({ asset: refinedAsset }),
})
}
+2 -6
View File
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -15,7 +16,7 @@
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
export { Prisma }
export * as $Enums from './enums.ts'
export * from './enums.ts'
export * from './enums.ts';
/**
* Model Asset
*
@@ -26,11 +27,6 @@ export type Asset = Prisma.AssetModel
*
*/
export type Character = Prisma.CharacterModel
/**
* Model Category
*
*/
export type Category = Prisma.CategoryModel
/**
* Model Equipment
*
+7 -15
View File
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -14,13 +15,13 @@ import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
import * as runtime from '@prisma/client/runtime/client'
import * as $Enums from './enums.ts'
import * as $Class from './internal/class.ts'
import * as Prisma from './internal/prismaNamespace.ts'
import * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums.ts"
import * as $Class from "./internal/class.ts"
import * as Prisma from "./internal/prismaNamespace.ts"
export * as $Enums from './enums.ts'
export * from './enums.ts'
export * from "./enums.ts"
/**
* ## Prisma Client
*
@@ -35,11 +36,7 @@ export * from './enums.ts'
* Read more in our [docs](https://pris.ly/d/client).
*/
export const PrismaClient = $Class.getPrismaClientClass()
export type PrismaClient<
LogOpts extends Prisma.LogLevel = never,
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Prisma.PrismaClientOptions['omit'],
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
export { Prisma }
/**
@@ -52,11 +49,6 @@ export type Asset = Prisma.AssetModel
*
*/
export type Character = Prisma.CharacterModel
/**
* Model Category
*
*/
export type Category = Prisma.CategoryModel
/**
* Model Equipment
*
+5 -3
View File
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -8,9 +9,10 @@
* 🟢 You can import this file directly.
*/
import type * as runtime from '@prisma/client/runtime/client'
import * as $Enums from './enums.ts'
import type * as Prisma from './internal/prismaNamespace.ts'
import type * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums.ts"
import type * as Prisma from "./internal/prismaNamespace.ts"
export type StringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
+7 -4
View File
@@ -1,12 +1,15 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file exports all enum related types from the schema.
*
* 🟢 You can import this file directly.
*/
* This file exports all enum related types from the schema.
*
* 🟢 You can import this file directly.
*/
// This file is empty because there are no enums in the schema.
export {}
+78 -121
View File
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -10,26 +11,26 @@
* Please import the `PrismaClient` class from the `client.ts` file instead.
*/
import * as runtime from '@prisma/client/runtime/client'
import type * as Prisma from './prismaNamespace.ts'
import * as runtime from "@prisma/client/runtime/client"
import type * as Prisma from "./prismaNamespace.ts"
const config: runtime.GetPrismaClientConfig = {
previewFeatures: ['driverAdapters'],
clientVersion: '7.3.0',
engineVersion: '9d6ad21cbbceab97458517b147a6a09ff43aa735',
activeProvider: 'sqlite',
inlineSchema:
'// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = "prisma-client"\n output = "../src/generated/prisma"\n previewFeatures = ["driverAdapters"]\n}\n\ndatasource db {\n provider = "sqlite"\n}\n\nmodel Asset {\n id String @id\n name String\n type String\n path String\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n // Relations\n characters Character[]\n equipments Equipment[]\n generations Generation[]\n\n @@map("assets")\n}\n\nmodel Character {\n id String @id\n name String\n description String?\n imageId String @map("image_id")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n // Relations\n image Asset @relation(fields: [imageId], references: [id])\n generations Generation[]\n\n @@map("characters")\n}\n\nmodel Category {\n id String @id\n name String\n parentId String? @map("parent_id")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n // Relations\n parent Category? @relation("CategoryToSubCategory", fields: [parentId], references: [id])\n subCategories Category[] @relation("CategoryToSubCategory")\n equipmentsAsMain Equipment[] @relation("CategoryMain")\n equipmentsAsSub Equipment[] @relation("CategorySub")\n\n @@map("categories")\n}\n\nmodel Equipment {\n id String @id\n name String\n description String\n categoryId String @map("category_id")\n subCategoryId String? @map("sub_category_id")\n imageId String @map("image_id")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n // Relations\n category Category @relation("CategoryMain", fields: [categoryId], references: [id])\n subCategory Category? @relation("CategorySub", fields: [subCategoryId], references: [id])\n image Asset @relation(fields: [imageId], references: [id])\n generations GenerationEquipment[]\n\n @@map("equipments")\n}\n\nmodel Generation {\n id String @id\n characterId String @map("character_id")\n imageId String @map("image_id")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @map("updated_at")\n\n // Relations\n character Character @relation(fields: [characterId], references: [id])\n image Asset @relation(fields: [imageId], references: [id])\n equipments GenerationEquipment[]\n\n @@map("generations")\n}\n\nmodel GenerationEquipment {\n generationId String @map("generation_id")\n equipmentId String @map("equipment_id")\n\n generation Generation @relation(fields: [generationId], references: [id])\n equipment Equipment @relation(fields: [equipmentId], references: [id])\n\n @@id([generationId, equipmentId])\n @@map("generation_equipments")\n}\n',
runtimeDataModel: {
models: {},
enums: {},
types: {},
},
"previewFeatures": [
"driverAdapters"
],
"clientVersion": "7.3.0",
"engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735",
"activeProvider": "sqlite",
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../src/generated/prisma\"\n previewFeatures = [\"driverAdapters\"]\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nmodel Asset {\n id String @id\n name String\n type String\n path String\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n // Relations\n characters Character[]\n equipments Equipment[]\n generations Generation[]\n\n @@map(\"assets\")\n}\n\nmodel Character {\n id String @id\n name String\n description String?\n imageId String @map(\"image_id\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n // Relations\n image Asset @relation(fields: [imageId], references: [id])\n generations Generation[]\n\n @@map(\"characters\")\n}\n\nmodel Equipment {\n id String @id\n name String\n description String\n category String\n subCategory String? @map(\"sub_category\")\n imageId String @map(\"image_id\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n // Relations\n image Asset @relation(fields: [imageId], references: [id])\n generations GenerationEquipment[]\n\n @@map(\"equipments\")\n}\n\nmodel Generation {\n id String @id\n characterId String @map(\"character_id\")\n imageId String @map(\"image_id\")\n createdAt DateTime @default(now()) @map(\"created_at\")\n updatedAt DateTime @updatedAt @map(\"updated_at\")\n\n // Relations\n character Character @relation(fields: [characterId], references: [id])\n image Asset @relation(fields: [imageId], references: [id])\n equipments GenerationEquipment[]\n\n @@map(\"generations\")\n}\n\nmodel GenerationEquipment {\n generationId String @map(\"generation_id\")\n equipmentId String @map(\"equipment_id\")\n\n generation Generation @relation(fields: [generationId], references: [id])\n equipment Equipment @relation(fields: [equipmentId], references: [id])\n\n @@id([generationId, equipmentId])\n @@map(\"generation_equipments\")\n}\n",
"runtimeDataModel": {
"models": {},
"enums": {},
"types": {}
}
}
config.runtimeDataModel = JSON.parse(
'{"models":{"Asset":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"name","kind":"scalar","type":"String"},{"name":"type","kind":"scalar","type":"String"},{"name":"path","kind":"scalar","type":"String"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"characters","kind":"object","type":"Character","relationName":"AssetToCharacter"},{"name":"equipments","kind":"object","type":"Equipment","relationName":"AssetToEquipment"},{"name":"generations","kind":"object","type":"Generation","relationName":"AssetToGeneration"}],"dbName":"assets"},"Character":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"name","kind":"scalar","type":"String"},{"name":"description","kind":"scalar","type":"String"},{"name":"imageId","kind":"scalar","type":"String","dbName":"image_id"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"image","kind":"object","type":"Asset","relationName":"AssetToCharacter"},{"name":"generations","kind":"object","type":"Generation","relationName":"CharacterToGeneration"}],"dbName":"characters"},"Category":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"name","kind":"scalar","type":"String"},{"name":"parentId","kind":"scalar","type":"String","dbName":"parent_id"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"parent","kind":"object","type":"Category","relationName":"CategoryToSubCategory"},{"name":"subCategories","kind":"object","type":"Category","relationName":"CategoryToSubCategory"},{"name":"equipmentsAsMain","kind":"object","type":"Equipment","relationName":"CategoryMain"},{"name":"equipmentsAsSub","kind":"object","type":"Equipment","relationName":"CategorySub"}],"dbName":"categories"},"Equipment":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"name","kind":"scalar","type":"String"},{"name":"description","kind":"scalar","type":"String"},{"name":"categoryId","kind":"scalar","type":"String","dbName":"category_id"},{"name":"subCategoryId","kind":"scalar","type":"String","dbName":"sub_category_id"},{"name":"imageId","kind":"scalar","type":"String","dbName":"image_id"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"category","kind":"object","type":"Category","relationName":"CategoryMain"},{"name":"subCategory","kind":"object","type":"Category","relationName":"CategorySub"},{"name":"image","kind":"object","type":"Asset","relationName":"AssetToEquipment"},{"name":"generations","kind":"object","type":"GenerationEquipment","relationName":"EquipmentToGenerationEquipment"}],"dbName":"equipments"},"Generation":{"fields":[{"name":"id","kind":"scalar","type":"String"},{"name":"characterId","kind":"scalar","type":"String","dbName":"character_id"},{"name":"imageId","kind":"scalar","type":"String","dbName":"image_id"},{"name":"createdAt","kind":"scalar","type":"DateTime","dbName":"created_at"},{"name":"updatedAt","kind":"scalar","type":"DateTime","dbName":"updated_at"},{"name":"character","kind":"object","type":"Character","relationName":"CharacterToGeneration"},{"name":"image","kind":"object","type":"Asset","relationName":"AssetToGeneration"},{"name":"equipments","kind":"object","type":"GenerationEquipment","relationName":"GenerationToGenerationEquipment"}],"dbName":"generations"},"GenerationEquipment":{"fields":[{"name":"generationId","kind":"scalar","type":"String","dbName":"generation_id"},{"name":"equipmentId","kind":"scalar","type":"String","dbName":"equipment_id"},{"name":"generation","kind":"object","type":"Generation","relationName":"GenerationToGenerationEquipment"},{"name":"equipment","kind":"object","type":"Equipment","relationName":"EquipmentToGenerationEquipment"}],"dbName":"generation_equipments"}},"enums":{},"types":{}}',
)
config.runtimeDataModel = JSON.parse("{\"models\":{\"Asset\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"path\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"characters\",\"kind\":\"object\",\"type\":\"Character\",\"relationName\":\"AssetToCharacter\"},{\"name\":\"equipments\",\"kind\":\"object\",\"type\":\"Equipment\",\"relationName\":\"AssetToEquipment\"},{\"name\":\"generations\",\"kind\":\"object\",\"type\":\"Generation\",\"relationName\":\"AssetToGeneration\"}],\"dbName\":\"assets\"},\"Character\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"imageId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"image_id\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"image\",\"kind\":\"object\",\"type\":\"Asset\",\"relationName\":\"AssetToCharacter\"},{\"name\":\"generations\",\"kind\":\"object\",\"type\":\"Generation\",\"relationName\":\"CharacterToGeneration\"}],\"dbName\":\"characters\"},\"Equipment\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"category\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"subCategory\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"sub_category\"},{\"name\":\"imageId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"image_id\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"image\",\"kind\":\"object\",\"type\":\"Asset\",\"relationName\":\"AssetToEquipment\"},{\"name\":\"generations\",\"kind\":\"object\",\"type\":\"GenerationEquipment\",\"relationName\":\"EquipmentToGenerationEquipment\"}],\"dbName\":\"equipments\"},\"Generation\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"characterId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"character_id\"},{\"name\":\"imageId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"image_id\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"created_at\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\",\"dbName\":\"updated_at\"},{\"name\":\"character\",\"kind\":\"object\",\"type\":\"Character\",\"relationName\":\"CharacterToGeneration\"},{\"name\":\"image\",\"kind\":\"object\",\"type\":\"Asset\",\"relationName\":\"AssetToGeneration\"},{\"name\":\"equipments\",\"kind\":\"object\",\"type\":\"GenerationEquipment\",\"relationName\":\"GenerationToGenerationEquipment\"}],\"dbName\":\"generations\"},\"GenerationEquipment\":{\"fields\":[{\"name\":\"generationId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"generation_id\"},{\"name\":\"equipmentId\",\"kind\":\"scalar\",\"type\":\"String\",\"dbName\":\"equipment_id\"},{\"name\":\"generation\",\"kind\":\"object\",\"type\":\"Generation\",\"relationName\":\"GenerationToGenerationEquipment\"},{\"name\":\"equipment\",\"kind\":\"object\",\"type\":\"Equipment\",\"relationName\":\"EquipmentToGenerationEquipment\"}],\"dbName\":\"generation_equipments\"}},\"enums\":{},\"types\":{}}")
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
const { Buffer } = await import('node:buffer')
@@ -38,26 +39,23 @@ async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Modul
}
config.compilerWasm = {
getRuntime: async () => await import('@prisma/client/runtime/query_compiler_fast_bg.sqlite.mjs'),
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_fast_bg.sqlite.mjs"),
getQueryCompilerWasmModule: async () => {
const { wasm } =
await import('@prisma/client/runtime/query_compiler_fast_bg.sqlite.wasm-base64.mjs')
const { wasm } = await import("@prisma/client/runtime/query_compiler_fast_bg.sqlite.wasm-base64.mjs")
return await decodeBase64AsWasm(wasm)
},
importName: './query_compiler_fast_bg.js',
importName: "./query_compiler_fast_bg.js"
}
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
'log' extends keyof ClientOptions
? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition>
? Prisma.GetEvents<ClientOptions['log']>
: never
: never
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
export interface PrismaClientConstructor {
/**
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
@@ -74,13 +72,9 @@ export interface PrismaClientConstructor {
new <
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
LogOpts extends LogOptions<Options> = LogOptions<Options>,
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U }
? U
: Prisma.PrismaClientOptions['omit'],
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
>(
options: Prisma.Subset<Options, Prisma.PrismaClientOptions>,
): PrismaClient<LogOpts, OmitOpts, ExtArgs>
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
}
/**
@@ -100,27 +94,23 @@ export interface PrismaClientConstructor {
export interface PrismaClient<
in LogOpts extends Prisma.LogLevel = never,
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
in out ExtArgs extends runtime.Types.Extensions.InternalArgs =
runtime.Types.Extensions.DefaultArgs,
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
> {
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
$on<V extends LogOpts>(
eventType: V,
callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void,
): PrismaClient
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
/**
* Connect with the database
*/
$connect(): runtime.Types.Utils.JsPromise<void>
$connect(): runtime.Types.Utils.JsPromise<void>;
/**
* Disconnect from the database
*/
$disconnect(): runtime.Types.Utils.JsPromise<void>
$disconnect(): runtime.Types.Utils.JsPromise<void>;
/**
/**
* Executes a prepared raw query and returns the number of affected rows.
* @example
* ```
@@ -129,10 +119,7 @@ export interface PrismaClient<
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$executeRaw<T = unknown>(
query: TemplateStringsArray | Prisma.Sql,
...values: any[]
): Prisma.PrismaPromise<number>
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
/**
* Executes a raw query and returns the number of affected rows.
@@ -144,7 +131,7 @@ export interface PrismaClient<
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
/**
* Performs a prepared raw query and returns the `SELECT` data.
@@ -155,10 +142,7 @@ export interface PrismaClient<
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$queryRaw<T = unknown>(
query: TemplateStringsArray | Prisma.Sql,
...values: any[]
): Prisma.PrismaPromise<T>
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
/**
* Performs a raw query and returns the `SELECT` data.
@@ -170,7 +154,8 @@ export interface PrismaClient<
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
/**
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
@@ -185,91 +170,63 @@ export interface PrismaClient<
*
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
*/
$transaction<P extends Prisma.PrismaPromise<any>[]>(
arg: [...P],
options?: { isolationLevel?: Prisma.TransactionIsolationLevel },
): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
$transaction<R>(
fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>,
options?: {
maxWait?: number
timeout?: number
isolationLevel?: Prisma.TransactionIsolationLevel
},
): runtime.Types.Utils.JsPromise<R>
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
$extends: runtime.Types.Extensions.ExtendsHook<
'extends',
Prisma.TypeMapCb<OmitOpts>,
ExtArgs,
runtime.Types.Utils.Call<
Prisma.TypeMapCb<OmitOpts>,
{
extArgs: ExtArgs
}
>
>
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
extArgs: ExtArgs
}>>
/**
/**
* `prisma.asset`: Exposes CRUD operations for the **Asset** model.
* Example usage:
* ```ts
* // Fetch zero or more Assets
* const assets = await prisma.asset.findMany()
* ```
*/
get asset(): Prisma.AssetDelegate<ExtArgs, { omit: OmitOpts }>
* Example usage:
* ```ts
* // Fetch zero or more Assets
* const assets = await prisma.asset.findMany()
* ```
*/
get asset(): Prisma.AssetDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.character`: Exposes CRUD operations for the **Character** model.
* Example usage:
* ```ts
* // Fetch zero or more Characters
* const characters = await prisma.character.findMany()
* ```
*/
get character(): Prisma.CharacterDelegate<ExtArgs, { omit: OmitOpts }>
/**
* `prisma.category`: Exposes CRUD operations for the **Category** model.
* Example usage:
* ```ts
* // Fetch zero or more Categories
* const categories = await prisma.category.findMany()
* ```
*/
get category(): Prisma.CategoryDelegate<ExtArgs, { omit: OmitOpts }>
* Example usage:
* ```ts
* // Fetch zero or more Characters
* const characters = await prisma.character.findMany()
* ```
*/
get character(): Prisma.CharacterDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.equipment`: Exposes CRUD operations for the **Equipment** model.
* Example usage:
* ```ts
* // Fetch zero or more Equipment
* const equipment = await prisma.equipment.findMany()
* ```
*/
get equipment(): Prisma.EquipmentDelegate<ExtArgs, { omit: OmitOpts }>
* Example usage:
* ```ts
* // Fetch zero or more Equipment
* const equipment = await prisma.equipment.findMany()
* ```
*/
get equipment(): Prisma.EquipmentDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.generation`: Exposes CRUD operations for the **Generation** model.
* Example usage:
* ```ts
* // Fetch zero or more Generations
* const generations = await prisma.generation.findMany()
* ```
*/
get generation(): Prisma.GenerationDelegate<ExtArgs, { omit: OmitOpts }>
* Example usage:
* ```ts
* // Fetch zero or more Generations
* const generations = await prisma.generation.findMany()
* ```
*/
get generation(): Prisma.GenerationDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.generationEquipment`: Exposes CRUD operations for the **GenerationEquipment** model.
* Example usage:
* ```ts
* // Fetch zero or more GenerationEquipments
* const generationEquipments = await prisma.generationEquipment.findMany()
* ```
*/
get generationEquipment(): Prisma.GenerationEquipmentDelegate<ExtArgs, { omit: OmitOpts }>
* Example usage:
* ```ts
* // Fetch zero or more GenerationEquipments
* const generationEquipments = await prisma.generationEquipment.findMany()
* ```
*/
get generationEquipment(): Prisma.GenerationEquipmentDelegate<ExtArgs, { omit: OmitOpts }>;
}
export function getPrismaClientClass(): PrismaClientConstructor {
+170 -254
View File
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -14,9 +15,9 @@
* model files in the `model` directory!
*/
import * as runtime from '@prisma/client/runtime/client'
import type * as Prisma from '../models.ts'
import { type PrismaClient } from './class.ts'
import * as runtime from "@prisma/client/runtime/client"
import type * as Prisma from "../models.ts"
import { type PrismaClient } from "./class.ts"
export type * from '../models.ts'
@@ -53,6 +54,8 @@ export const raw = runtime.raw
export const Sql = runtime.Sql
export type Sql = runtime.Sql
/**
* Decimal.js
*/
@@ -62,8 +65,8 @@ export type Decimal = runtime.Decimal
export type DecimalJsLike = runtime.DecimalJsLike
/**
* Extensions
*/
* Extensions
*/
export type Extension = runtime.Types.Extensions.UserArgs
export const getExtensionContext = runtime.Extensions.getExtensionContext
export type Args<T, F extends runtime.Operation> = runtime.Types.Public.Args<T, F>
@@ -81,8 +84,8 @@ export type PrismaVersion = {
* Query Engine version: 9d6ad21cbbceab97458517b147a6a09ff43aa735
*/
export const prismaVersion: PrismaVersion = {
client: '7.3.0',
engine: '9d6ad21cbbceab97458517b147a6a09ff43aa735',
client: "7.3.0",
engine: "9d6ad21cbbceab97458517b147a6a09ff43aa735"
}
/**
@@ -97,10 +100,11 @@ export type InputJsonObject = runtime.InputJsonObject
export type InputJsonArray = runtime.InputJsonArray
export type InputJsonValue = runtime.InputJsonValue
export const NullTypes = {
DbNull: runtime.NullTypes.DbNull as new (secret: never) => typeof runtime.DbNull,
JsonNull: runtime.NullTypes.JsonNull as new (secret: never) => typeof runtime.JsonNull,
AnyNull: runtime.NullTypes.AnyNull as new (secret: never) => typeof runtime.AnyNull,
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
}
/**
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
@@ -123,6 +127,7 @@ export const JsonNull = runtime.JsonNull
*/
export const AnyNull = runtime.AnyNull
type SelectAndInclude = {
select: any
include: any
@@ -137,18 +142,18 @@ type SelectAndOmit = {
* From T, pick a set of properties whose keys are in the union K
*/
type Prisma__Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
[P in K]: T[P];
};
export type Enumerable<T> = T | Array<T>
export type Enumerable<T> = T | Array<T>;
/**
* Subset
* @desc From `T` pick properties that exist in `U`. Simple version of Intersection
*/
export type Subset<T, U> = {
[key in keyof T]: key extends keyof U ? T[key] : never
}
[key in keyof T]: key extends keyof U ? T[key] : never;
};
/**
* SelectSubset
@@ -157,11 +162,12 @@ export type Subset<T, U> = {
*/
export type SelectSubset<T, U> = {
[key in keyof T]: key extends keyof U ? T[key] : never
} & (T extends SelectAndInclude
? 'Please either choose `select` or `include`.'
: T extends SelectAndOmit
? 'Please either choose `select` or `omit`.'
: {})
} &
(T extends SelectAndInclude
? 'Please either choose `select` or `include`.'
: T extends SelectAndOmit
? 'Please either choose `select` or `omit`.'
: {})
/**
* Subset + Intersection
@@ -169,35 +175,37 @@ export type SelectSubset<T, U> = {
*/
export type SubsetIntersection<T, U, K> = {
[key in keyof T]: key extends keyof U ? T[key] : never
} & K
} &
K
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
/**
* XOR is needed to have a real mutually exclusive union type
* https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types
*/
export type XOR<T, U> = T extends object
? U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: U
: T
export type XOR<T, U> =
T extends object ?
U extends object ?
(Without<T, U> & U) | (Without<U, T> & T)
: U : T
/**
* Is T a Record?
*/
type IsObject<T extends any> =
T extends Array<any>
? False
: T extends Date
? False
: T extends Uint8Array
? False
: T extends BigInt
? False
: T extends object
? True
: False
type IsObject<T extends any> = T extends Array<any>
? False
: T extends Date
? False
: T extends Uint8Array
? False
: T extends BigInt
? False
: T extends object
? True
: False
/**
* If it's T[], return T
@@ -218,14 +226,20 @@ type EitherStrict<O extends object, K extends Key> = Strict<__Either<O, K>>
type EitherLoose<O extends object, K extends Key> = ComputeRaw<__Either<O, K>>
type _Either<O extends object, K extends Key, strict extends Boolean> = {
type _Either<
O extends object,
K extends Key,
strict extends Boolean
> = {
1: EitherStrict<O, K>
0: EitherLoose<O, K>
}[strict]
export type Either<O extends object, K extends Key, strict extends Boolean = 1> = O extends unknown
? _Either<O, K, strict>
: never
export type Either<
O extends object,
K extends Key,
strict extends Boolean = 1
> = O extends unknown ? _Either<O, K, strict> : never
export type Union = any
@@ -234,67 +248,56 @@ export type PatchUndefined<O extends object, O1 extends object> = {
} & {}
/** Helper Types for "Merge" **/
export type IntersectOf<U extends Union> = (U extends unknown ? (k: U) => void : never) extends (
k: infer I,
) => void
export type IntersectOf<U extends Union> = (
U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
export type Overwrite<O extends object, O1 extends object> = {
[K in keyof O]: K extends keyof O1 ? O1[K] : O[K]
} & {}
[K in keyof O]: K extends keyof O1 ? O1[K] : O[K];
} & {};
type _Merge<U extends object> = IntersectOf<
Overwrite<
U,
{
[K in keyof U]-?: At<U, K>
}
>
>
type _Merge<U extends object> = IntersectOf<Overwrite<U, {
[K in keyof U]-?: At<U, K>;
}>>;
type Key = string | number | symbol
type AtStrict<O extends object, K extends Key> = O[K & keyof O]
type AtLoose<O extends object, K extends Key> = O extends unknown ? AtStrict<O, K> : never
type Key = string | number | symbol;
type AtStrict<O extends object, K extends Key> = O[K & keyof O];
type AtLoose<O extends object, K extends Key> = O extends unknown ? AtStrict<O, K> : never;
export type At<O extends object, K extends Key, strict extends Boolean = 1> = {
1: AtStrict<O, K>
0: AtLoose<O, K>
}[strict]
1: AtStrict<O, K>;
0: AtLoose<O, K>;
}[strict];
export type ComputeRaw<A extends any> = A extends Function
? A
: {
[K in keyof A]: A[K]
} & {}
export type ComputeRaw<A extends any> = A extends Function ? A : {
[K in keyof A]: A[K];
} & {};
export type OptionalFlat<O> = {
[K in keyof O]?: O[K]
} & {}
[K in keyof O]?: O[K];
} & {};
type _Record<K extends keyof any, T> = {
[P in K]: T
}
[P in K]: T;
};
// cause typescript not to expand types and preserve names
type NoExpand<T> = T extends unknown ? T : never
type NoExpand<T> = T extends unknown ? T : never;
// this type assumes the passed object is entirely optional
export type AtLeast<O extends object, K extends string> = NoExpand<
O extends unknown
?
| (K extends keyof O ? { [P in K]: O[P] } & O : O)
| ({ [P in keyof O as P extends K ? P : never]-?: O[P] } & O)
: never
>
? | (K extends keyof O ? { [P in K]: O[P] } & O : O)
| {[P in keyof O as P extends K ? P : never]-?: O[P]} & O
: never>;
type _Strict<U, _U = U> = U extends unknown
? U & OptionalFlat<_Record<Exclude<Keys<_U>, keyof U>, never>>
: never
type _Strict<U, _U = U> = U extends unknown ? U & OptionalFlat<_Record<Exclude<Keys<_U>, keyof U>, never>> : never;
export type Strict<U extends object> = ComputeRaw<_Strict<U>>
export type Strict<U extends object> = ComputeRaw<_Strict<U>>;
/** End Helper Types for "Merge" **/
export type Merge<U extends object> = ComputeRaw<_Merge<Strict<U>>>
export type Merge<U extends object> = ComputeRaw<_Merge<Strict<U>>>;
export type Boolean = True | False
@@ -310,10 +313,12 @@ export type Not<B extends Boolean> = {
export type Extends<A1 extends any, A2 extends any> = [A1] extends [never]
? 0 // anything `never` is false
: A1 extends A2
? 1
: 0
? 1
: 0
export type Has<U extends Union, U1 extends Union> = Not<Extends<Exclude<U1, U>, U1>>
export type Has<U extends Union, U1 extends Union> = Not<
Extends<Exclude<U1, U>, U1>
>
export type Or<B1 extends Boolean, B2 extends Boolean> = {
0: {
@@ -328,17 +333,22 @@ export type Or<B1 extends Boolean, B2 extends Boolean> = {
export type Keys<U extends Union> = U extends unknown ? keyof U : never
export type GetScalarType<T, O> = O extends object
? {
[P in keyof T]: P extends keyof O ? O[P] : never
}
: never
export type GetScalarType<T, O> = O extends object ? {
[P in keyof T]: P extends keyof O
? O[P]
: never
} : never
type FieldPaths<T, U = Omit<T, '_avg' | '_sum' | '_count' | '_min' | '_max'>> =
IsObject<T> extends True ? U : T
type FieldPaths<
T,
U = Omit<T, '_avg' | '_sum' | '_count' | '_min' | '_max'>
> = IsObject<T> extends True ? U : T
export type GetHavingFields<T> = {
[K in keyof T]: Or<Or<Extends<'OR', K>, Extends<'AND', K>>, Extends<'NOT', K>> extends True
[K in keyof T]: Or<
Or<Extends<'OR', K>, Extends<'AND', K>>,
Extends<'NOT', K>
> extends True
? // infer is only needed to not hit TS limit
// based on the brilliant idea of Pierre-Antoine Mills
// https://github.com/microsoft/TypeScript/issues/30188#issuecomment-478938437
@@ -346,8 +356,8 @@ export type GetHavingFields<T> = {
? GetHavingFields<UnEnumerate<TK> extends object ? Merge<UnEnumerate<TK>> : never>
: never
: {} extends FieldPaths<T[K]>
? never
: K
? never
: K
}[keyof T]
/**
@@ -360,53 +370,41 @@ export type MaybeTupleToUnion<T> = T extends any[] ? TupleToUnion<T> : T
/**
* Like `Pick`, but additionally can also accept an array of keys
*/
export type PickEnumerable<T, K extends Enumerable<keyof T> | keyof T> = Prisma__Pick<
T,
MaybeTupleToUnion<K>
>
export type PickEnumerable<T, K extends Enumerable<keyof T> | keyof T> = Prisma__Pick<T, MaybeTupleToUnion<K>>
/**
* Exclude all keys with underscores
*/
export type ExcludeUnderscoreKeys<T extends string> = T extends `_${string}` ? never : T
export type FieldRef<Model, FieldType> = runtime.FieldRef<Model, FieldType>
type FieldRefInputType<Model, FieldType> = Model extends never ? never : FieldRef<Model, FieldType>
export const ModelName = {
Asset: 'Asset',
Character: 'Character',
Category: 'Category',
Equipment: 'Equipment',
Generation: 'Generation',
GenerationEquipment: 'GenerationEquipment',
GenerationEquipment: 'GenerationEquipment'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
export interface TypeMapCb<GlobalOmitOptions = {}> extends runtime.Types.Utils.Fn<
{ extArgs: runtime.Types.Extensions.InternalArgs },
runtime.Types.Utils.Record<string, any>
> {
export interface TypeMapCb<GlobalOmitOptions = {}> extends runtime.Types.Utils.Fn<{extArgs: runtime.Types.Extensions.InternalArgs }, runtime.Types.Utils.Record<string, any>> {
returns: TypeMap<this['params']['extArgs'], GlobalOmitOptions>
}
export type TypeMap<
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
GlobalOmitOptions = {},
> = {
export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> = {
globalOmitOptions: {
omit: GlobalOmitOptions
}
meta: {
modelProps:
| 'asset'
| 'character'
| 'category'
| 'equipment'
| 'generation'
| 'generationEquipment'
modelProps: "asset" | "character" | "equipment" | "generation" | "generationEquipment"
txIsolationLevel: TransactionIsolationLevel
}
model: {
@@ -558,80 +556,6 @@ export type TypeMap<
}
}
}
Category: {
payload: Prisma.$CategoryPayload<ExtArgs>
fields: Prisma.CategoryFieldRefs
operations: {
findUnique: {
args: Prisma.CategoryFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload> | null
}
findUniqueOrThrow: {
args: Prisma.CategoryFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>
}
findFirst: {
args: Prisma.CategoryFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload> | null
}
findFirstOrThrow: {
args: Prisma.CategoryFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>
}
findMany: {
args: Prisma.CategoryFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>[]
}
create: {
args: Prisma.CategoryCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>
}
createMany: {
args: Prisma.CategoryCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.CategoryCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>[]
}
delete: {
args: Prisma.CategoryDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>
}
update: {
args: Prisma.CategoryUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>
}
deleteMany: {
args: Prisma.CategoryDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.CategoryUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.CategoryUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>[]
}
upsert: {
args: Prisma.CategoryUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$CategoryPayload>
}
aggregate: {
args: Prisma.CategoryAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateCategory>
}
groupBy: {
args: Prisma.CategoryGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.CategoryGroupByOutputType>[]
}
count: {
args: Prisma.CategoryCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.CategoryCountAggregateOutputType> | number
}
}
}
Equipment: {
payload: Prisma.$EquipmentPayload<ExtArgs>
fields: Prisma.EquipmentFieldRefs
@@ -850,9 +774,7 @@ export type TypeMap<
}
count: {
args: Prisma.GenerationEquipmentCountArgs<ExtArgs>
result:
| runtime.Types.Utils.Optional<Prisma.GenerationEquipmentCountAggregateOutputType>
| number
result: runtime.Types.Utils.Optional<Prisma.GenerationEquipmentCountAggregateOutputType> | number
}
}
}
@@ -862,19 +784,19 @@ export type TypeMap<
payload: any
operations: {
$executeRaw: {
args: [query: TemplateStringsArray | Sql, ...values: any[]]
args: [query: TemplateStringsArray | Sql, ...values: any[]],
result: any
}
$executeRawUnsafe: {
args: [query: string, ...values: any[]]
args: [query: string, ...values: any[]],
result: any
}
$queryRaw: {
args: [query: TemplateStringsArray | Sql, ...values: any[]]
args: [query: TemplateStringsArray | Sql, ...values: any[]],
result: any
}
$queryRawUnsafe: {
args: [query: string, ...values: any[]]
args: [query: string, ...values: any[]],
result: any
}
}
@@ -886,11 +808,11 @@ export type TypeMap<
*/
export const TransactionIsolationLevel = runtime.makeStrictEnum({
Serializable: 'Serializable',
Serializable: 'Serializable'
} as const)
export type TransactionIsolationLevel =
(typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export const AssetScalarFieldEnum = {
id: 'id',
@@ -898,100 +820,99 @@ export const AssetScalarFieldEnum = {
type: 'type',
path: 'path',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type AssetScalarFieldEnum = (typeof AssetScalarFieldEnum)[keyof typeof AssetScalarFieldEnum]
export const CharacterScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
imageId: 'imageId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type CharacterScalarFieldEnum =
(typeof CharacterScalarFieldEnum)[keyof typeof CharacterScalarFieldEnum]
export type CharacterScalarFieldEnum = (typeof CharacterScalarFieldEnum)[keyof typeof CharacterScalarFieldEnum]
export const CategoryScalarFieldEnum = {
id: 'id',
name: 'name',
parentId: 'parentId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
} as const
export type CategoryScalarFieldEnum =
(typeof CategoryScalarFieldEnum)[keyof typeof CategoryScalarFieldEnum]
export const EquipmentScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
categoryId: 'categoryId',
subCategoryId: 'subCategoryId',
category: 'category',
subCategory: 'subCategory',
imageId: 'imageId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type EquipmentScalarFieldEnum =
(typeof EquipmentScalarFieldEnum)[keyof typeof EquipmentScalarFieldEnum]
export type EquipmentScalarFieldEnum = (typeof EquipmentScalarFieldEnum)[keyof typeof EquipmentScalarFieldEnum]
export const GenerationScalarFieldEnum = {
id: 'id',
characterId: 'characterId',
imageId: 'imageId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type GenerationScalarFieldEnum =
(typeof GenerationScalarFieldEnum)[keyof typeof GenerationScalarFieldEnum]
export type GenerationScalarFieldEnum = (typeof GenerationScalarFieldEnum)[keyof typeof GenerationScalarFieldEnum]
export const GenerationEquipmentScalarFieldEnum = {
generationId: 'generationId',
equipmentId: 'equipmentId',
equipmentId: 'equipmentId'
} as const
export type GenerationEquipmentScalarFieldEnum =
(typeof GenerationEquipmentScalarFieldEnum)[keyof typeof GenerationEquipmentScalarFieldEnum]
export type GenerationEquipmentScalarFieldEnum = (typeof GenerationEquipmentScalarFieldEnum)[keyof typeof GenerationEquipmentScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc',
desc: 'desc'
} as const
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullsOrder = {
first: 'first',
last: 'last',
last: 'last'
} as const
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
/**
* Field references
*/
/**
* Reference to a field of type 'String'
*/
export type StringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String'>
/**
* Reference to a field of type 'DateTime'
*/
export type DateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'DateTime'>
/**
* Reference to a field of type 'Int'
*/
export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int'>
/**
* Batch Payload for updateMany & deleteMany & createMany
*/
@@ -999,30 +920,22 @@ export type BatchPayload = {
count: number
}
export const defineExtension = runtime.Extensions
.defineExtension as unknown as runtime.Types.Extensions.ExtendsHook<
'define',
TypeMapCb,
runtime.Types.Extensions.DefaultArgs
>
export const defineExtension = runtime.Extensions.defineExtension as unknown as runtime.Types.Extensions.ExtendsHook<"define", TypeMapCb, runtime.Types.Extensions.DefaultArgs>
export type DefaultPrismaClient = PrismaClient
export type ErrorFormat = 'pretty' | 'colorless' | 'minimal'
export type PrismaClientOptions = (
| {
/**
* Instance of a Driver Adapter, e.g., like one provided by `@prisma/adapter-pg`.
*/
adapter: runtime.SqlDriverAdapterFactory
accelerateUrl?: never
}
| {
/**
* Prisma Accelerate URL allowing the client to connect through Accelerate instead of a direct database.
*/
accelerateUrl: string
adapter?: never
}
) & {
export type PrismaClientOptions = ({
/**
* Instance of a Driver Adapter, e.g., like one provided by `@prisma/adapter-pg`.
*/
adapter: runtime.SqlDriverAdapterFactory
accelerateUrl?: never
} | {
/**
* Prisma Accelerate URL allowing the client to connect through Accelerate instead of a direct database.
*/
accelerateUrl: string
adapter?: never
}) & {
/**
* @default "colorless"
*/
@@ -1097,7 +1010,6 @@ export type PrismaClientOptions = (
export type GlobalOmitConfig = {
asset?: Prisma.AssetOmit
character?: Prisma.CharacterOmit
category?: Prisma.CategoryOmit
equipment?: Prisma.EquipmentOmit
generation?: Prisma.GenerationOmit
generationEquipment?: Prisma.GenerationEquipmentOmit
@@ -1110,12 +1022,15 @@ export type LogDefinition = {
emit: 'stdout' | 'event'
}
export type CheckIsLogLevel<T> = T extends LogLevel ? T : never
export type CheckIsLogLevel<T> = T extends LogLevel ? T : never;
export type GetLogType<T> = CheckIsLogLevel<T extends LogDefinition ? T['level'] : T>
export type GetLogType<T> = CheckIsLogLevel<
T extends LogDefinition ? T['level'] : T
>;
export type GetEvents<T extends any[]> =
T extends Array<LogLevel | LogDefinition> ? GetLogType<T[number]> : never
export type GetEvents<T extends any[]> = T extends Array<LogLevel | LogDefinition>
? GetLogType<T[number]>
: never;
export type QueryEvent = {
timestamp: Date
@@ -1132,6 +1047,7 @@ export type LogEvent = {
}
/* End Types for Logging */
export type PrismaAction =
| 'findUnique'
| 'findUniqueOrThrow'
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -14,17 +15,18 @@
* model files in the `model` directory!
*/
import * as runtime from '@prisma/client/runtime/index-browser'
import * as runtime from "@prisma/client/runtime/index-browser"
export type * from '../models.ts'
export type * from './prismaNamespace.ts'
export const Decimal = runtime.Decimal
export const NullTypes = {
DbNull: runtime.NullTypes.DbNull as new (secret: never) => typeof runtime.DbNull,
JsonNull: runtime.NullTypes.JsonNull as new (secret: never) => typeof runtime.JsonNull,
AnyNull: runtime.NullTypes.AnyNull as new (secret: never) => typeof runtime.AnyNull,
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
}
/**
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
@@ -47,13 +49,13 @@ export const JsonNull = runtime.JsonNull
*/
export const AnyNull = runtime.AnyNull
export const ModelName = {
Asset: 'Asset',
Character: 'Character',
Category: 'Category',
Equipment: 'Equipment',
Generation: 'Generation',
GenerationEquipment: 'GenerationEquipment',
GenerationEquipment: 'GenerationEquipment'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
@@ -63,11 +65,11 @@ export type ModelName = (typeof ModelName)[keyof typeof ModelName]
*/
export const TransactionIsolationLevel = runtime.makeStrictEnum({
Serializable: 'Serializable',
Serializable: 'Serializable'
} as const)
export type TransactionIsolationLevel =
(typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export const AssetScalarFieldEnum = {
id: 'id',
@@ -75,77 +77,68 @@ export const AssetScalarFieldEnum = {
type: 'type',
path: 'path',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type AssetScalarFieldEnum = (typeof AssetScalarFieldEnum)[keyof typeof AssetScalarFieldEnum]
export const CharacterScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
imageId: 'imageId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type CharacterScalarFieldEnum =
(typeof CharacterScalarFieldEnum)[keyof typeof CharacterScalarFieldEnum]
export type CharacterScalarFieldEnum = (typeof CharacterScalarFieldEnum)[keyof typeof CharacterScalarFieldEnum]
export const CategoryScalarFieldEnum = {
id: 'id',
name: 'name',
parentId: 'parentId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
} as const
export type CategoryScalarFieldEnum =
(typeof CategoryScalarFieldEnum)[keyof typeof CategoryScalarFieldEnum]
export const EquipmentScalarFieldEnum = {
id: 'id',
name: 'name',
description: 'description',
categoryId: 'categoryId',
subCategoryId: 'subCategoryId',
category: 'category',
subCategory: 'subCategory',
imageId: 'imageId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type EquipmentScalarFieldEnum =
(typeof EquipmentScalarFieldEnum)[keyof typeof EquipmentScalarFieldEnum]
export type EquipmentScalarFieldEnum = (typeof EquipmentScalarFieldEnum)[keyof typeof EquipmentScalarFieldEnum]
export const GenerationScalarFieldEnum = {
id: 'id',
characterId: 'characterId',
imageId: 'imageId',
createdAt: 'createdAt',
updatedAt: 'updatedAt',
updatedAt: 'updatedAt'
} as const
export type GenerationScalarFieldEnum =
(typeof GenerationScalarFieldEnum)[keyof typeof GenerationScalarFieldEnum]
export type GenerationScalarFieldEnum = (typeof GenerationScalarFieldEnum)[keyof typeof GenerationScalarFieldEnum]
export const GenerationEquipmentScalarFieldEnum = {
generationId: 'generationId',
equipmentId: 'equipmentId',
equipmentId: 'equipmentId'
} as const
export type GenerationEquipmentScalarFieldEnum =
(typeof GenerationEquipmentScalarFieldEnum)[keyof typeof GenerationEquipmentScalarFieldEnum]
export type GenerationEquipmentScalarFieldEnum = (typeof GenerationEquipmentScalarFieldEnum)[keyof typeof GenerationEquipmentScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc',
desc: 'desc'
} as const
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullsOrder = {
first: 'first',
last: 'last',
last: 'last'
} as const
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
+2 -2
View File
@@ -1,3 +1,4 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
@@ -9,8 +10,7 @@
*/
export type * from './models/Asset.ts'
export type * from './models/Character.ts'
export type * from './models/Category.ts'
export type * from './models/Equipment.ts'
export type * from './models/Generation.ts'
export type * from './models/GenerationEquipment.ts'
export type * from './commonInputTypes.ts'
export type * from './commonInputTypes.ts'
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+54
View File
@@ -0,0 +1,54 @@
export const EQUIPMENT_CATEGORIES = [
{
main_category: 'Headwear',
sub_categories: ['Helmet', 'Hat', 'Cap', 'Hood', 'Circlet', 'Mask', 'Crown', 'Others'],
},
{
main_category: 'Top',
sub_categories: ['Shirt', 'Jacket', 'Hoodie', 'Vest', 'Tunic', 'Armor_Chest', 'Bra_Top', 'Cape', 'Others'],
},
{
main_category: 'Bottom',
sub_categories: ['Pants', 'Shorts', 'Skirt', 'Leggings', 'Hakama', 'Armor_Legs', 'Others'],
},
{
main_category: 'Full_Body',
sub_categories: ['Robe', 'Jumpsuit', 'Dress', 'Armor_Suit', 'Exosuit', 'Others'],
},
{
main_category: 'Handwear',
sub_categories: ['Gloves', 'Gauntlets', 'Bracers', 'Wristbands', 'Rings', 'Others'],
},
{
main_category: 'Footwear',
sub_categories: ['Boots', 'Sneakers', 'Sandals', 'Heels', 'Greaves', 'Others'],
},
{
main_category: 'Weapon',
sub_categories: ['Sword', 'Greatsword', 'Dagger', 'Spear', 'Bow', 'Firearm', 'Shield', 'Staff', 'Others'],
},
{
main_category: 'Accessory',
sub_categories: ['Scarf', 'Necklace', 'Belt', 'Sash', 'Glasses', 'Goggles', 'Earpiece', 'Wing', 'Others'],
},
{
main_category: 'Storage',
sub_categories: ['Quiver', 'Scabbard', 'Pouch', 'Backpack', 'Holster', 'Bandolier', 'Others'],
},
{
main_category: 'Utility_Prop',
sub_categories: ['Gadget', 'Phone', 'Flashlight', 'Potion', 'Tool', 'Book_Scroll', 'Others'],
},
{
main_category: 'Others',
sub_categories: ['Others'],
},
] as const
export type MainCategory = (typeof EQUIPMENT_CATEGORIES)[number]['main_category']
export type SubCategory = (typeof EQUIPMENT_CATEGORIES)[number]['sub_categories'][number]
export function getSubCategories(mainCategory: string): readonly string[] {
const category = EQUIPMENT_CATEGORIES.find((c) => c.main_category === mainCategory)
return category ? category.sub_categories : []
}
-58
View File
@@ -1,58 +0,0 @@
import { prisma } from '../db'
import { ulid } from 'ulidx'
export class CategoryService {
/**
* Finds a category by name (case-insensitive) or creates it if it doesn't exist.
* If a parentId is provided, it links the category as a sub-category.
*/
async findOrCreate(name: string, parentId?: string): Promise<string> {
const normalizedName = name.trim()
// Check if category exists
const existing = await prisma.category.findFirst({
where: {
name: {
equals: normalizedName,
// mode: 'insensitive' // SQLite doesn't support 'mode: insensitive' directly like Postgres, usually need manual lower() or collate NOCASE
},
parentId: parentId || null,
},
})
if (existing) {
return existing.id
}
// Create new category
const id = ulid()
const newCategory = await prisma.category.create({
data: {
id,
name: normalizedName,
parentId: parentId || null,
},
})
return newCategory.id
}
/**
* Lists all main categories with their sub-categories.
*/
async listAll() {
return prisma.category.findMany({
where: {
parentId: null,
},
include: {
subCategories: true,
},
orderBy: {
name: 'asc',
},
})
}
}
export const categoryService = new CategoryService()
+2 -2
View File
@@ -23,12 +23,12 @@ describe('EquipmentService', () => {
mockPrisma.equipment.count.mockResolvedValue(5)
mockPrisma.equipment.findMany.mockResolvedValue([])
await equipmentService.listEquipments({ page: 1, limit: 10 }, { categoryId: 'cat1' })
await equipmentService.listEquipments({ page: 1, limit: 10 }, { category: 'Headwear' })
expect(mockPrisma.equipment.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: expect.objectContaining({
categoryId: 'cat1',
category: 'Headwear',
}),
}),
)
+9 -12
View File
@@ -8,17 +8,17 @@ export class EquipmentService {
*/
async listEquipments(
pagination: { page: number; limit: number },
filters?: { categoryId?: string; subCategoryId?: string },
filters?: { category?: string; subCategory?: string },
) {
const { page, limit } = pagination
const skip = (page - 1) * limit
const where: Prisma.EquipmentWhereInput = {}
if (filters?.categoryId) {
where.categoryId = filters.categoryId
if (filters?.category) {
where.category = filters.category
}
if (filters?.subCategoryId) {
where.subCategoryId = filters.subCategoryId
if (filters?.subCategory) {
where.subCategory = filters.subCategory
}
const [total, items] = await Promise.all([
@@ -29,8 +29,6 @@ export class EquipmentService {
take: limit,
include: {
image: true,
category: true,
subCategory: true,
},
orderBy: {
createdAt: 'desc',
@@ -48,8 +46,8 @@ export class EquipmentService {
name: string
description: string
imageId: string
categoryId: string
subCategoryId?: string
category: string
subCategory?: string
}) {
const id = ulid()
@@ -59,12 +57,11 @@ export class EquipmentService {
name: data.name,
description: data.description,
imageId: data.imageId,
categoryId: data.categoryId,
subCategoryId: data.subCategoryId,
category: data.category,
subCategory: data.subCategory,
},
include: {
image: true,
category: true,
},
})
}
+2 -1
View File
@@ -10,7 +10,8 @@ mock.module('./ai', () => ({
item_name: 'Helmet',
description: 'A rusty helmet',
background_color: 'Red',
category: 'Head',
category: 'Headwear',
sub_category: 'Helmet',
},
],
})),
+36 -6
View File
@@ -1,13 +1,15 @@
import { aiService } from './ai'
import sharp from 'sharp'
import { z } from 'zod'
import { EQUIPMENT_CATEGORIES } from '../lib/categories'
// Types for the extraction process
export interface ExtractedAsset {
item_name: string
description: string
background_color: string
category: string // Added category
category: string
sub_category?: string
}
export interface AnalysisResponse {
@@ -31,15 +33,21 @@ export class ExtractionService {
* Analyzes an image to identify extractable assets.
*/
async analyzeImage(file: File): Promise<AnalysisResponse> {
const categoriesJson = JSON.stringify(EQUIPMENT_CATEGORIES, null, 2)
const prompt = `
Task: Character Asset Identification for Extraction
Instructions:
1. Analyze the character in the image and identify all distinct, extractable equipment and clothing items.
2. Filter for items that are clearly visible: tops, bottoms, headwear, footwear, specialized armor (arm guards, leg guards), and accessories (scarves, belts, capes).
2. Filter for items that are clearly visible.
3. For each identified item, assign one of the following high-contrast background colors for future segmentation: Red, Yellow, Green, White, Blue, Black, Magenta, Cyan, Orange, Gray. Avoid repeating colors if possible.
4. Limit to a maximum of 9 most prominent assets.
5. Assign a general category to each item (e.g., Head, Body, Legs, Feet, Weapon, Accessory).
5. Assign a category and sub-category to each item based on the provided list.
- Choose the best fitting "main_category" and "sub_categories".
- If no suitable category/sub-category is found, use "Others".
Available Categories:
${categoriesJson}
Output Format (Strict JSON):
Return ONLY a JSON object with a list of assets.
@@ -51,7 +59,8 @@ Return ONLY a JSON object with a list of assets.
item_name: z.string().describe('Name of the item'),
background_color: z.string().describe('Background color assigned'),
description: z.string().describe('Description of the item'),
category: z.string().describe('Category of the item'),
category: z.string().describe('Main category of the item'),
sub_category: z.string().optional().describe('Sub category of the item'),
}),
),
})
@@ -108,10 +117,20 @@ Output Requirement: High-resolution texture sheet, flat lay presentation, sharp
/**
* Detects bounding boxes for assets in the texture sheet.
*/
async detectBoundingBoxes(sheetBase64: string): Promise<BoundingBoxResponse> {
async detectBoundingBoxes(
sheetBase64: string,
analyzedAssets: ExtractedAsset[],
): Promise<BoundingBoxResponse> {
const buffer = Buffer.from(sheetBase64.replace(/^data:image\/\w+;base64,/, ''), 'base64')
const file = new File([buffer], 'sheet.png', { type: 'image/png' })
const assetContext = analyzedAssets
.map(
(a) =>
`- Name: ${a.item_name}, Description: ${a.description}, Background Color: ${a.background_color}`,
)
.join('\n')
const prompt = `
Task: Spatial Asset Detection & Normalized Grid Mapping
@@ -122,11 +141,20 @@ Instructions:
- [ymin, xmin, ymax, xmax]
- (0, 0) is the top-left corner; (1.0, 1.0) is the bottom-right corner.
4. Ensure the box coordinates strictly encompass the outer boundary of each colored tile.
5. Use the provided asset context to correctly identify which item is in which box.
Asset Context:
${assetContext}
Output Format (Strict JSON):
Return ONLY a JSON object. Provide the grid dimensions and a list of detected assets with their coordinates.
`
// Create an enum from the analyzed asset names plus "unknown"
const assetNames = analyzedAssets.map((a) => a.item_name)
// Zod enum requires at least one value, and they must be unique
const uniqueNames = Array.from(new Set([...assetNames, 'unknown'])) as [string, ...string[]]
const schema = z.object({
grid_dimensions: z.object({
rows: z.number(),
@@ -134,7 +162,7 @@ Return ONLY a JSON object. Provide the grid dimensions and a list of detected as
}),
assets: z.array(
z.object({
item_name: z.string(),
item_name: z.enum(uniqueNames),
coordinates: z
.tuple([z.number(), z.number(), z.number(), z.number()])
.describe('[ymin, xmin, ymax, xmax]'),
@@ -162,6 +190,8 @@ Return ONLY a JSON object. Provide the grid dimensions and a list of detected as
const croppedAssets = []
for (const box of boxes.assets) {
if (box.item_name === 'unknown') continue
const [ymin, xmin, ymax, xmax] = box.coordinates
const left = Math.floor(xmin * metadata.width)
+9 -1
View File
@@ -74,8 +74,16 @@ export const Extractor: React.FC<ExtractorProps> = ({
if (event === 'status') {
onStatusChange(data.status, data.message)
} else if (event === 'asset_refined') {
// progressively update results
const newAsset = data.asset
setResults((prev) => [...prev, newAsset])
} else if (event === 'complete') {
onStatusChange('complete', 'Extraction complete!')
// Assuming data.assets contains all assets, but we have been accumulating them.
// We can just rely on the accumulated results if we trust asset_refined.
// But let's sync one last time or just keep what we have.
// The API sends all assets in complete event too.
setResults(data.assets)
onAssetsExtracted(data.assets)
} else if (event === 'error') {
@@ -181,7 +189,7 @@ export const Extractor: React.FC<ExtractorProps> = ({
</div>
<div className="p-3 bg-white border-t border-stone-200 rounded-b-lg">
<div className="text-xs font-bold text-stone-700 truncate">{item.name}</div>
<div className="text-[10px] text-stone-400 truncate">{item.category.name}</div>
<div className="text-[10px] text-stone-400 truncate">{item.category}</div>
<div className="w-6 h-1 bg-amber-400 rounded-full mt-2"></div>
</div>
</div>
+1 -1
View File
@@ -130,7 +130,7 @@ export const Stash: React.FC<StashProps> = ({ onNavigateToExtractor }) => {
</h3>
</div>
<p className="text-[10px] text-stone-400 font-mono uppercase tracking-wider truncate">
{item.category?.name || 'Unknown'}
{item.category || 'Unknown'}
</p>
</div>
</div>
+2 -6
View File
@@ -1,13 +1,9 @@
export interface Category {
id: string
name: string
}
export interface ExtractedAsset {
id: string
name: string
description: string
category: Category
category: string
subCategory?: string
imageUrl: string
}