Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 26 additions & 25 deletions src/lib/cart.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,31 @@
import type { CartItem } from "@/models/cart.model";
import type { Cart, CartItem } from "@/models/cart.model";
import { type Product } from "@/models/product.model";
import {
alterQuantityCartItem,
deleteRemoteCartItem,
getRemoteCart,
getOrCreateCart,
} from "@/services/cart.service";
import { getProductById } from "@/services/product.service";

export async function getCart(request: Request) {
export async function getCart(userId?: number, sessionCartId?: string) {
try {
const remoteCart = await getRemoteCart(request);

// Si ya existe un carrito (con ítems o vacío), lo devolvemos
if (remoteCart) {
// Si no existe la propiedad total, calcúlala sumando las cantidades de cada ítem
if (remoteCart.total === undefined) {
remoteCart.total =
remoteCart.items?.reduce((total, item) => total + item.quantity, 0) ||
0;
}
return remoteCart;
}

// No se encontró carrito
return null;
return getOrCreateCart(userId, sessionCartId);
} catch (error) {
console.error(error);
return null;
}
}

export async function addToCart(
request: Request,
userId: number | undefined,
sessionCartId: string | undefined,
productId: Product["id"],
quantity: number = 1
) {
try {
const product = await getProductById(request, productId);
const updatedCart = await alterQuantityCartItem(
request,
product.id,
userId,
sessionCartId,
productId,
quantity
);
return updatedCart;
Expand All @@ -49,13 +35,28 @@ export async function addToCart(
}
}

export async function removeFromCart(request: Request, itemId: CartItem["id"]) {
export async function removeFromCart(
userId: number | undefined,
sessionCartId: string | undefined,
itemId: CartItem["id"]
) {
try {
// El backend determinará si es un usuario autenticado o invitado
const updatedCart = await deleteRemoteCartItem(request, itemId);
const updatedCart = await deleteRemoteCartItem(
userId,
sessionCartId,
itemId
);
return updatedCart;
} catch (error) {
console.error(error);
return null;
}
}

export function calculateTotal(cart: Cart) {
return cart.items.reduce(
(total, item) => total + item.product.price * item.quantity,
0
);
}
4 changes: 2 additions & 2 deletions src/lib/client.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export async function serverClient<T>(
const cookieHeader = request.headers.get("Cookie");
const session = await getSession(cookieHeader);
const token = session.get("token");
const cartSessionId = session.get("cartSessionId");
const sessionCartId = session.get("sessionCartId");

const cartSessionHeader: { "x-cart-id": string } | Record<string, string> =
cartSessionId ? { "x-cart-id": cartSessionId } : {};
sessionCartId ? { "x-cart-id": sessionCartId } : {};

const config: RequestInit = {
method: body ? "POST" : "GET",
Expand Down
14 changes: 10 additions & 4 deletions src/models/cart.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import { type User } from "./user.model";

export interface CartItem {
id: number;
product: Product;
product: Pick<
Product,
"id" | "title" | "imgSrc" | "alt" | "price" | "isOnSale"
>;
quantity: number;
createdAt: string;
updatedAt: string;
}

export interface Cart {
id: number;
userId?: User["id"];
sessionCartId?: string;
userId: User["id"] | null;
sessionCartId: string;
items: CartItem[];
total: number;
createdAt: string;
updatedAt: string;
}

export interface CartItemInput {
Expand Down
7 changes: 5 additions & 2 deletions src/models/product.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Category } from "./category.model";
// import { type Category } from "./category.model";

export interface Product {
id: number;
Expand All @@ -7,7 +7,10 @@ export interface Product {
alt: string | null;
price: number;
description: string | null;
categorySlug: Category["slug"];
categoryId: number;
// categorySlug: Category["slug"];
isOnSale: boolean;
features: string[];
createdAt: string;
updatedAt: string;
}
224 changes: 224 additions & 0 deletions src/repositories/cart.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import * as db from "@/db";
import { type Cart, type CartItem } from "@/models/cart.model";

export async function getCart(
userId: number | undefined,
sessionCartId: string | undefined,
id?: number
): Promise<Cart | null> {
let whereClause: string;
let paramValue: number | string;

if (userId) {
whereClause = "WHERE c.user_id = $1";
paramValue = userId;
} else if (sessionCartId) {
whereClause = "WHERE c.session_cart_id = $1";
paramValue = sessionCartId;
} else if (id) {
whereClause = "WHERE c.id = $1";
paramValue = id;
} else {
// Si no se proporciona ningún identificador, devolvemos null
return null;
}

const query = `
SELECT
c.*,
COALESCE(
(
SELECT json_agg(
json_build_object(
'id', ci.id,
'quantity', ci.quantity,
'product', (
SELECT json_build_object(
'id', p.id,
'title', p.title,
'imgSrc', p.img_src,
'alt', p.alt,
'price', p.price,
'isOnSale', p.is_on_sale
)
FROM products p
WHERE p.id = ci.product_id
),
'createdAt', ci.created_at,
'updatedAt', ci.updated_at
)
ORDER BY ci.id ASC
)
FROM cart_items ci
LEFT JOIN products pr on pr.id = ci.product_id
WHERE ci.cart_id = c.id
)::json,
'[]'::json
) as items
FROM carts c
${whereClause}
`;
return await db.queryOne<Cart>(query, [paramValue]);
}

export async function createCart(): Promise<Cart | null> {
const query = "INSERT INTO carts DEFAULT VALUES RETURNING *";
const cart = await db.queryOne<Cart>(query);
return getCart(undefined, undefined, cart?.id);
}

// export async function createGuestCart(sessionCartId: string): Promise<Cart | null> { // new function
// const query = "INSERT INTO carts (session_cart_id) VALUES ($1) RETURNING *";
// return db.queryOne<Cart>(query, [sessionCartId]);
// }

export async function addCartItem(
cartId: number,
productId: number,
quantity: number
): Promise<CartItem | null> {
const query = `
INSERT INTO cart_items (cart_id, product_id, quantity)
VALUES ($1, $2, $3)
RETURNING *
`;

return await db.queryOne<CartItem>(query, [cartId, productId, quantity]);
}

export async function addCartItems(
cartId: number,
items: { productId: number; quantity: number }[] | []
): Promise<CartItem[]> {
// Si no hay elementos para agregar, retornar un array vacío
if (items.length === 0) {
return [];
}

const valuesClause = items
.map((_, i) => `($1, $${i * 2 + 2}, $${i * 2 + 3})`)
.join(",");

const query = `
INSERT INTO cart_items (cart_id, product_id, quantity)
VALUES ${valuesClause}
RETURNING *
`;

const values = items.reduce(
(acc, item) => {
acc.push(item.productId, item.quantity);
return acc;
},
[cartId] as (string | number)[]
);

return await db.query<CartItem>(query, values);
}

export async function updateCartItem(
cartId: number,
itemId: number,
quantity: number
): Promise<CartItem | null> {
const query =
"UPDATE cart_items SET quantity = $1 WHERE id = $2 AND cart_id = $3 RETURNING *";

return await db.queryOne<CartItem>(query, [quantity, itemId, cartId]);
}

export async function removeCartItem(
cartId: number,
itemId: number
): Promise<void> {
const query = "DELETE FROM cart_items WHERE id = $1 AND cart_id = $2";
await db.query(query, [itemId, cartId]);
}

export async function clearCart(cartId: number): Promise<void> {
const query = "DELETE FROM carts WHERE id = $1";
await db.query(query, [cartId]);
}

export async function updateCartWithUserId(
cartId: number,
userId: number
): Promise<Cart | null> {
const query = `
UPDATE carts
SET user_id = $2
WHERE id = $1
RETURNING *
`;

return await db.queryOne<Cart>(query, [cartId, userId]);
}

export async function updateCartBySessionId(
sessionCartId: string,
userId: number
): Promise<Cart | null> {
const query = `
UPDATE carts
SET user_id = $2
WHERE session_cart_id = $1
RETURNING *
`;

return await db.queryOne<Cart>(query, [sessionCartId, userId]);
}

export async function mergeGuestCartWithUserCart(
userId: number | undefined,
sessionCartId: string
): Promise<Cart | null> {
// Primero, obtenemos el carrito del usuario y el carrito de invitado
const userCart = await getCart(userId, undefined);
const guestCart = await getCart(undefined, sessionCartId);

if (!guestCart) {
return userCart;
}

if (!userCart) {
// Si el usuario no tiene carrito, actualizamos el carrito de invitado con el ID del usuario
const query = `
UPDATE carts
SET user_id = $1
WHERE session_cart_id = $2
RETURNING *
`;
return await db.queryOne<Cart>(query, [userId, sessionCartId]);
}

// Eliminamos productos del carrito usuario que también existan en el carrito invitado
await db.query(
`
DELETE FROM cart_items
WHERE cart_id = $1
AND product_id IN (
SELECT product_id FROM cart_items WHERE cart_id = $2
)
`,
[userCart.id, guestCart.id]
);

// Insertamos los artículos del carrito invitado al carrito usuario
const query = `
INSERT INTO cart_items (cart_id, product_id, quantity)
SELECT $1, product_id, quantity
FROM cart_items
WHERE cart_id = $2
RETURNING *
`;

await db.query(query, [userCart.id, guestCart.id]);

// Eliminamos el carrito de invitado
await db.query(`DELETE FROM carts WHERE session_cart_id = $1`, [
sessionCartId,
]);

// Devolvemos el carrito actualizado del usuario
return await getCart(userId, undefined);
}
18 changes: 18 additions & 0 deletions src/repositories/product.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as db from "@/db";
import { type Product } from "@/models/product.model";

export async function getAllProducts(): Promise<Product[]> {
return await db.query<Product>("SELECT * FROM products");
}

export async function getProductById(id: number): Promise<Product | null> {
const query = "SELECT * FROM products WHERE id = $1";
return await db.queryOne<Product>(query, [id]);
}

export async function getProductsByCategory(
categoryId: number
): Promise<Product[]> {
const query = "SELECT * FROM products WHERE category_id = $1";
return await db.query<Product>(query, [categoryId]);
}
Loading