294 lines
8.1 KiB
TypeScript
294 lines
8.1 KiB
TypeScript
import { desc, eq, type Database } from "@pkg/db";
|
|
import { product } from "@pkg/db/schema";
|
|
import { getError, Logger } from "@pkg/logger";
|
|
import { ERROR_CODES, type Result } from "@pkg/result";
|
|
import { nanoid } from "nanoid";
|
|
import {
|
|
productModel,
|
|
type CreateProductPayload,
|
|
type ProductModel,
|
|
type UpdateProductPayload,
|
|
} from "./data";
|
|
|
|
export class ProductRepository {
|
|
private db: Database;
|
|
|
|
constructor(db: Database) {
|
|
this.db = db;
|
|
}
|
|
|
|
async listAllProducts(): Promise<Result<ProductModel[]>> {
|
|
try {
|
|
const results = await this.db.query.product.findMany({
|
|
orderBy: [desc(product.createdAt)],
|
|
});
|
|
const out = [] as ProductModel[];
|
|
for (const result of results) {
|
|
const parsed = productModel.safeParse(result);
|
|
if (!parsed.success) {
|
|
Logger.error("Failed to parse product");
|
|
Logger.error(parsed.error);
|
|
continue;
|
|
}
|
|
out.push(parsed.data);
|
|
}
|
|
return { data: out };
|
|
} catch (e) {
|
|
return {
|
|
error: getError(
|
|
{
|
|
code: ERROR_CODES.DATABASE_ERROR,
|
|
message: "Failed to fetch products",
|
|
detail:
|
|
"An error occurred while retrieving products from the database",
|
|
userHint: "Please try refreshing the page",
|
|
actionable: false,
|
|
},
|
|
e,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
async getProductById(id: number): Promise<Result<ProductModel>> {
|
|
try {
|
|
const result = await this.db.query.product.findFirst({
|
|
where: eq(product.id, id),
|
|
});
|
|
|
|
if (!result) {
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.NOT_FOUND_ERROR,
|
|
message: "Product not found",
|
|
detail: "No product exists with the provided ID",
|
|
userHint: "Please check the product ID and try again",
|
|
actionable: true,
|
|
}),
|
|
};
|
|
}
|
|
|
|
const parsed = productModel.safeParse(result);
|
|
if (!parsed.success) {
|
|
Logger.error("Failed to parse product", result);
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
|
message: "Failed to parse product",
|
|
userHint: "Please try again",
|
|
detail: "Failed to parse product",
|
|
}),
|
|
};
|
|
}
|
|
return { data: parsed.data };
|
|
} catch (e) {
|
|
return {
|
|
error: getError(
|
|
{
|
|
code: ERROR_CODES.DATABASE_ERROR,
|
|
message: "Failed to fetch product",
|
|
detail:
|
|
"An error occurred while retrieving the product from the database",
|
|
userHint: "Please try refreshing the page",
|
|
actionable: false,
|
|
},
|
|
e,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
async createProduct(payload: CreateProductPayload): Promise<Result<number>> {
|
|
try {
|
|
// Generate a unique linkId
|
|
const linkId = nanoid(16);
|
|
|
|
const result = await this.db
|
|
.insert(product)
|
|
.values({
|
|
linkId,
|
|
title: payload.title,
|
|
description: payload.description,
|
|
longDescription: payload.longDescription,
|
|
price: payload.price.toString(),
|
|
discountPrice: payload.discountPrice.toString(),
|
|
})
|
|
.returning({ id: product.id })
|
|
.execute();
|
|
|
|
if (!result || result.length === 0) {
|
|
throw new Error("Failed to create product record");
|
|
}
|
|
|
|
return { data: result[0].id };
|
|
} catch (e) {
|
|
return {
|
|
error: getError(
|
|
{
|
|
code: ERROR_CODES.DATABASE_ERROR,
|
|
message: "Failed to create product",
|
|
detail: "An error occurred while creating the product",
|
|
userHint: "Please try again",
|
|
actionable: false,
|
|
},
|
|
e,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
async updateProduct(payload: UpdateProductPayload): Promise<Result<boolean>> {
|
|
try {
|
|
if (!payload.id) {
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.VALIDATION_ERROR,
|
|
message: "Invalid product ID",
|
|
detail: "No product ID was provided for the update operation",
|
|
userHint: "Please provide a valid product ID",
|
|
actionable: true,
|
|
}),
|
|
};
|
|
}
|
|
|
|
// Check if product exists
|
|
const existing = await this.db.query.product.findFirst({
|
|
where: eq(product.id, payload.id),
|
|
});
|
|
|
|
if (!existing) {
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.NOT_FOUND_ERROR,
|
|
message: "Product not found",
|
|
detail: "No product exists with the provided ID",
|
|
userHint: "Please check the product ID and try again",
|
|
actionable: true,
|
|
}),
|
|
};
|
|
}
|
|
|
|
// Build the update object with only the fields that are provided
|
|
const updateValues: Record<string, any> = {};
|
|
|
|
if (payload.title !== undefined) updateValues.title = payload.title;
|
|
if (payload.description !== undefined)
|
|
updateValues.description = payload.description;
|
|
if (payload.longDescription !== undefined)
|
|
updateValues.longDescription = payload.longDescription;
|
|
if (payload.price !== undefined)
|
|
updateValues.price = payload.price.toString();
|
|
if (payload.discountPrice !== undefined)
|
|
updateValues.discountPrice = payload.discountPrice.toString();
|
|
updateValues.updatedAt = new Date();
|
|
|
|
await this.db
|
|
.update(product)
|
|
.set(updateValues)
|
|
.where(eq(product.id, payload.id))
|
|
.execute();
|
|
|
|
return { data: true };
|
|
} catch (e) {
|
|
return {
|
|
error: getError(
|
|
{
|
|
code: ERROR_CODES.DATABASE_ERROR,
|
|
message: "Failed to update product",
|
|
detail: "An error occurred while updating the product",
|
|
userHint: "Please try again",
|
|
actionable: false,
|
|
},
|
|
e,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
async deleteProduct(id: number): Promise<Result<boolean>> {
|
|
try {
|
|
// Check if product exists
|
|
const existing = await this.db.query.product.findFirst({
|
|
where: eq(product.id, id),
|
|
});
|
|
|
|
if (!existing) {
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.NOT_FOUND_ERROR,
|
|
message: "Product not found",
|
|
detail: "No product exists with the provided ID",
|
|
userHint: "Please check the product ID and try again",
|
|
actionable: true,
|
|
}),
|
|
};
|
|
}
|
|
|
|
await this.db.delete(product).where(eq(product.id, id)).execute();
|
|
|
|
return { data: true };
|
|
} catch (e) {
|
|
return {
|
|
error: getError(
|
|
{
|
|
code: ERROR_CODES.DATABASE_ERROR,
|
|
message: "Failed to delete product",
|
|
detail: "An error occurred while deleting the product",
|
|
userHint: "Please try again",
|
|
actionable: false,
|
|
},
|
|
e,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
async refreshProductLinkId(id: number): Promise<Result<string>> {
|
|
try {
|
|
// Check if product exists
|
|
const existing = await this.db.query.product.findFirst({
|
|
where: eq(product.id, id),
|
|
});
|
|
|
|
if (!existing) {
|
|
return {
|
|
error: getError({
|
|
code: ERROR_CODES.NOT_FOUND_ERROR,
|
|
message: "Product not found",
|
|
detail: "No product exists with the provided ID",
|
|
userHint: "Please check the product ID and try again",
|
|
actionable: true,
|
|
}),
|
|
};
|
|
}
|
|
|
|
// Generate a new unique linkId
|
|
const newLinkId = nanoid(16);
|
|
|
|
await this.db
|
|
.update(product)
|
|
.set({
|
|
linkId: newLinkId,
|
|
updatedAt: new Date(),
|
|
})
|
|
.where(eq(product.id, id))
|
|
.execute();
|
|
|
|
return { data: newLinkId };
|
|
} catch (e) {
|
|
return {
|
|
error: getError(
|
|
{
|
|
code: ERROR_CODES.DATABASE_ERROR,
|
|
message: "Failed to refresh product link ID",
|
|
detail: "An error occurred while refreshing the product's link ID",
|
|
userHint: "Please try again",
|
|
actionable: false,
|
|
},
|
|
e,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
}
|