feat(ADM-018): completed feature
This commit is contained in:
111
project/src/modules/cart/infrastructure/pg-cart-repository.ts
Normal file
111
project/src/modules/cart/infrastructure/pg-cart-repository.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user