feat(ADM-018): completed feature
This commit is contained in:
181
project/src/modules/orders/infrastructure/pg-order-repository.ts
Normal file
181
project/src/modules/orders/infrastructure/pg-order-repository.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
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<OrderView> {
|
||||
const client = await this.pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const orderResult = await client.query<OrderRow>(
|
||||
`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<ItemRow>(
|
||||
`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<OrderView[]> {
|
||||
const result = await this.pool.query<OrderRow>(
|
||||
'SELECT * FROM orders_orders ORDER BY created_at DESC',
|
||||
);
|
||||
return Promise.all(
|
||||
result.rows.map(async (order) => {
|
||||
const items = await this.pool.query<ItemRow>(
|
||||
'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<OrderView | undefined> {
|
||||
const orderResult = await this.pool.query<OrderRow>(
|
||||
'SELECT * FROM orders_orders WHERE id = $1',
|
||||
[id],
|
||||
);
|
||||
const order = orderResult.rows[0];
|
||||
if (!order) return undefined;
|
||||
const items = await this.pool.query<ItemRow>(
|
||||
'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<OrderView | undefined> {
|
||||
const orderResult = await this.pool.query<OrderRow>(
|
||||
'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<ItemRow>(
|
||||
'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<OrderView | undefined> {
|
||||
const result = await this.pool.query<OrderRow>(
|
||||
`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<ItemRow>(
|
||||
'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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user