feat(ADM-018): completed feature

This commit is contained in:
chattie
2026-08-17 22:23:10 +02:00
parent cf1c69fc8b
commit d595b4871f
871 changed files with 47411 additions and 281 deletions

View File

@@ -0,0 +1,111 @@
import type pg from 'pg';
import type { Cart, CartItem } from '../domain/cart.js';
import type { CartRepository } from '../domain/ports.js';
import type { CartItemInput } from '../domain/cart.js';
interface CartRow {
id: string;
user_id: string;
promo_code: string | null;
created_at: Date;
updated_at: Date;
}
interface ItemRow {
id: string;
cart_id: string;
product_id: string;
variant_id: string;
quantity: number;
created_at: Date;
updated_at: Date;
}
export class PgCartRepository implements CartRepository {
constructor(private readonly pool: pg.Pool) {}
async getOrCreate(userId: string): Promise<Cart> {
const cart = await this.ensureCart(userId);
return this.loadCart(cart);
}
async addItem(userId: string, input: CartItemInput): Promise<Cart> {
const cart = await this.ensureCart(userId);
await this.pool.query(
`INSERT INTO cart_items (cart_id, product_id, variant_id, quantity)
VALUES ($1, $2, $3, $4)
ON CONFLICT (cart_id, variant_id) DO UPDATE
SET quantity = cart_items.quantity + EXCLUDED.quantity, updated_at = now()`,
[cart.id, input.productId, input.variantId, input.quantity],
);
return this.loadCart(cart);
}
async changeQuantity(userId: string, variantId: string, quantity: number): Promise<Cart> {
const cart = await this.ensureCart(userId);
await this.pool.query(
`UPDATE cart_items SET quantity = $3, updated_at = now()
WHERE cart_id = $1 AND variant_id = $2`,
[cart.id, variantId, quantity],
);
return this.loadCart(cart);
}
async removeItem(userId: string, variantId: string): Promise<Cart> {
const cart = await this.ensureCart(userId);
await this.pool.query('DELETE FROM cart_items WHERE cart_id = $1 AND variant_id = $2', [
cart.id,
variantId,
]);
return this.loadCart(cart);
}
async setPromoCode(userId: string, code: string | null): Promise<Cart> {
const cart = await this.ensureCart(userId);
const result = await this.pool.query<CartRow>(
'UPDATE cart_carts SET promo_code = $2, updated_at = now() WHERE id = $1 RETURNING *',
[cart.id, code],
);
const row = result.rows[0];
if (!row) throw new Error('cart_carts promo update returned no row');
return this.loadCart(row);
}
private async ensureCart(userId: string): Promise<CartRow> {
const result = await this.pool.query<CartRow>(
`INSERT INTO cart_carts (user_id) VALUES ($1)
ON CONFLICT (user_id) DO UPDATE SET updated_at = cart_carts.updated_at
RETURNING *`,
[userId],
);
const row = result.rows[0];
if (!row) throw new Error('cart_carts upsert returned no row');
return row;
}
private async loadCart(row: CartRow): Promise<Cart> {
const result = await this.pool.query<ItemRow>(
'SELECT * FROM cart_items WHERE cart_id = $1 ORDER BY created_at, id',
[row.id],
);
return {
id: row.id,
userId: row.user_id,
promoCode: row.promo_code,
items: result.rows.map(toItem),
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
}
function toItem(row: ItemRow): CartItem {
return {
id: row.id,
cartId: row.cart_id,
productId: row.product_id,
variantId: row.variant_id,
quantity: row.quantity,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}