import type pg from 'pg'; import type { OrderRepository } from '../domain/ports.js'; import type { Order, OrderItem, OrderItemInput, OrderState, OrderView } from '../domain/order.js'; interface OrderRow { id: string; user_id: string; idempotency_key: string | null; state: OrderState; currency: 'EUR'; subtotal_cents: number; discount_cents: number; tax_cents: number; total_cents: number; created_at: Date; updated_at: Date; } interface ItemRow { id: string; order_id: string; product_id: string; variant_id: string; sku: string; ean: string | null; name: string; unit_price_cents: number; discount_cents: number; tax_cents: number; quantity: number; created_at: Date; } interface CreateInput { userId: string; idempotencyKey: string | null; items: OrderItemInput[]; totals: { subtotalCents: number; discountCents: number; taxCents: number; totalCents: number }; } export class PgOrderRepository implements OrderRepository { constructor(private readonly pool: pg.Pool) {} async create(input: CreateInput): Promise { const client = await this.pool.connect(); try { await client.query('BEGIN'); const orderResult = await client.query( `INSERT INTO orders_orders (user_id, idempotency_key, subtotal_cents, discount_cents, tax_cents, total_cents) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`, [ input.userId, input.idempotencyKey, input.totals.subtotalCents, input.totals.discountCents, input.totals.taxCents, input.totals.totalCents, ], ); const order = orderResult.rows[0]; if (!order) throw new Error('orders_orders INSERT returned no row'); for (const item of input.items) { await client.query( `INSERT INTO orders_items (order_id, product_id, variant_id, sku, ean, name, unit_price_cents, discount_cents, tax_cents, quantity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`, [ order.id, item.productId, item.variantId, item.sku, item.ean, item.name, item.unitPriceCents, item.discountCents, item.taxCents, item.quantity, ], ); } await client.query('COMMIT'); const loaded = await this.findById(order.id); if (!loaded) throw new Error('orders_orders load after create returned no row'); return loaded; } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } } async findAll(): Promise { const result = await this.pool.query( 'SELECT * FROM orders_orders ORDER BY created_at DESC', ); return Promise.all( result.rows.map(async (order) => { const items = await this.pool.query( 'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY id', [order.id], ); return { ...toOrder(order), items: items.rows.map(toItem) }; }), ); } async findById(id: string): Promise { const orderResult = await this.pool.query( 'SELECT * FROM orders_orders WHERE id = $1', [id], ); const order = orderResult.rows[0]; if (!order) return undefined; const items = await this.pool.query( 'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY created_at, id', [id], ); return { ...toOrder(order), items: items.rows.map(toItem) }; } async findByIdAndUserId(id: string, userId: string): Promise { const orderResult = await this.pool.query( 'SELECT * FROM orders_orders WHERE id = $1 AND user_id = $2', [id, userId], ); const order = orderResult.rows[0]; if (!order) return undefined; const items = await this.pool.query( 'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY created_at, id', [id], ); return { ...toOrder(order), items: items.rows.map(toItem) }; } async updateState(id: string, state: OrderState): Promise { const result = await this.pool.query( `UPDATE orders_orders SET state = $2, updated_at = now() WHERE id = $1 RETURNING *`, [id, state], ); const order = result.rows[0]; if (!order) return undefined; const items = await this.pool.query( 'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY created_at, id', [id], ); return { ...toOrder(order), items: items.rows.map(toItem) }; } } function toOrder(row: OrderRow): Order { return { id: row.id, userId: row.user_id, idempotencyKey: row.idempotency_key, state: row.state, currency: row.currency, subtotalCents: row.subtotal_cents, discountCents: row.discount_cents, taxCents: row.tax_cents, totalCents: row.total_cents, createdAt: row.created_at, updatedAt: row.updated_at, }; } function toItem(row: ItemRow): OrderItem { return { id: row.id, orderId: row.order_id, productId: row.product_id, variantId: row.variant_id, sku: row.sku, ean: row.ean, name: row.name, unitPriceCents: row.unit_price_cents, discountCents: row.discount_cents, taxCents: row.tax_cents, quantity: row.quantity, createdAt: row.created_at, }; }