feat(F-086): completed feature

This commit is contained in:
chattie
2026-08-20 06:13:37 +02:00
parent 7ece045e13
commit 822bc7546c
16 changed files with 201 additions and 27 deletions

View File

@@ -92,6 +92,7 @@ const newProductSchema = z.object({
seoDescription: z.string().min(1).max(500).optional().nullable(),
categoryIds: z.array(z.uuid()).max(50).optional(),
brandId: z.uuid().optional().nullable(),
expirationDate: z.iso.date().optional().nullable(),
});
const productPatchSchema = newProductSchema

View File

@@ -50,6 +50,7 @@ export interface Product {
categoryIds: string[];
brandId: string | null;
brand?: ProductBrandSummary;
expirationDate: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -66,6 +67,7 @@ export interface NewProduct {
seoDescription?: string | null;
categoryIds?: string[];
brandId?: string | null;
expirationDate?: string | null;
}
/** Fields a product update may set. Undefined = leave unchanged. */

View File

@@ -25,6 +25,7 @@ export interface ProductRow {
category_ids: string[] | null;
brand_name: string | null;
brand_slug: string | null;
expiration_date: string | null;
created_at: Date;
updated_at: Date;
}
@@ -53,6 +54,7 @@ const UPDATABLE: ReadonlyArray<[keyof ProductPatch, string]> = [
['seoTitle', 'seo_title'],
['seoDescription', 'seo_description'],
['brandId', 'brand_id'],
['expirationDate', 'expiration_date'],
];
export class PgProductRepository implements ProductRepository {
@@ -91,8 +93,8 @@ export class PgProductRepository implements ProductRepository {
try {
await client.query('BEGIN');
const result = await client.query<ProductRow>(
`INSERT INTO catalog_products (name, slug, description, state, seo_title, seo_description, brand_id)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`INSERT INTO catalog_products (name, slug, description, state, seo_title, seo_description, brand_id, expiration_date)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *, ARRAY[]::uuid[] AS category_ids`,
[
input.name,
@@ -102,6 +104,7 @@ export class PgProductRepository implements ProductRepository {
input.seoTitle ?? null,
input.seoDescription ?? null,
input.brandId ?? null,
input.expirationDate ?? null,
],
);
const row = result.rows[0];
@@ -292,6 +295,7 @@ export function toProduct(row: ProductRow): Product {
? { id: row.brand_id, name: row.brand_name, slug: row.brand_slug }
: undefined,
categoryIds: row.category_ids ?? [],
expirationDate: row.expiration_date ?? null,
createdAt: row.created_at,
updatedAt: row.updated_at,
};

View File

@@ -16,6 +16,7 @@ function product(input: Partial<Product> & Pick<Product, 'id' | 'name' | 'slug'>
seoDescription: null,
categoryIds: [],
brandId: null,
expirationDate: null,
createdAt: new Date('2026-01-01T00:00:00Z'),
updatedAt: new Date('2026-01-01T00:00:00Z'),
...input,

View File

@@ -20,6 +20,7 @@ function product(input: Partial<Product> & Pick<Product, 'id' | 'name' | 'slug'>
seoDescription: null,
categoryIds: [],
brandId: null,
expirationDate: null,
createdAt: new Date('2026-01-01T00:00:00Z'),
updatedAt: new Date('2026-01-01T00:00:00Z'),
...input,