From 27d0e0149d48f34dda81d47346b98d8af61d0931 Mon Sep 17 00:00:00 2001 From: MastersGame Date: Thu, 5 Nov 2026 23:00:48 +0100 Subject: [PATCH] feat(db): add database repositories Upserts keyed on hiboutikId and deletion of the rows that no longer exist in Hiboutik. --- src/database/brand.database.repository.ts | 36 +++++++++++++++ src/database/category.database.repository.ts | 44 +++++++++++++++++++ src/database/customer.database.repository.ts | 36 +++++++++++++++ src/database/product.database.repository.ts | 44 +++++++++++++++++++ .../productVersion.database.repository.ts | 36 +++++++++++++++ src/database/supplier.database.repository.ts | 36 +++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 src/database/brand.database.repository.ts create mode 100644 src/database/category.database.repository.ts create mode 100644 src/database/customer.database.repository.ts create mode 100644 src/database/product.database.repository.ts create mode 100644 src/database/productVersion.database.repository.ts create mode 100644 src/database/supplier.database.repository.ts diff --git a/src/database/brand.database.repository.ts b/src/database/brand.database.repository.ts new file mode 100644 index 0000000..70ebcad --- /dev/null +++ b/src/database/brand.database.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { Brand } from '../../prisma/generated/client'; +import { BrandCreateInput } from '../../prisma/generated/models/Brand'; + +/** Database access to brands. Rows are matched with Hiboutik by `hiboutikId`. */ +@Injectable() +export class BrandDatabaseRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAll(): Promise { + return this.prisma.brand.findMany({ + orderBy: { hiboutikId: 'asc' }, + }); + } + + /** Creates or updates the brand with the same `hiboutikId`. */ + async upsert(brand: BrandCreateInput): Promise { + return this.prisma.brand.upsert({ + where: { hiboutikId: brand.hiboutikId }, + create: brand, + update: brand, + }); + } + + /** + * Deletes the brands that no longer exist in Hiboutik. + * Their products are kept: the relation is set to null (onDelete: SetNull). + */ + async deleteMissing(keptHiboutikIds: number[]): Promise { + const result = await this.prisma.brand.deleteMany({ + where: { hiboutikId: { notIn: keptHiboutikIds } }, + }); + return result.count; + } +} diff --git a/src/database/category.database.repository.ts b/src/database/category.database.repository.ts new file mode 100644 index 0000000..2775133 --- /dev/null +++ b/src/database/category.database.repository.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { Category } from '../../prisma/generated/client'; +import { CategoryCreateInput } from '../../prisma/generated/models/Category'; + +/** Database access to categories. Rows are matched with Hiboutik by `hiboutikId`. */ +@Injectable() +export class CategoryDatabaseRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAll(): Promise { + return this.prisma.category.findMany({ + orderBy: { hiboutikId: 'asc' }, + }); + } + + /** Creates or updates the category with the same `hiboutikId`. */ + async upsert(category: CategoryCreateInput): Promise { + return this.prisma.category.upsert({ + where: { hiboutikId: category.hiboutikId }, + create: category, + update: category, + }); + } + + /** Links a category to its parent (null for a root category). Both must already exist. */ + async setParent(hiboutikId: number, parentHiboutikId: number | null): Promise { + return this.prisma.category.update({ + where: { hiboutikId }, + data: { parent: parentHiboutikId ? { connect: { hiboutikId: parentHiboutikId } } : { disconnect: true } }, + }); + } + + /** + * Deletes the categories that no longer exist in Hiboutik. + * Their products and sub-categories are kept: the relation is set to null (onDelete: SetNull). + */ + async deleteMissing(keptHiboutikIds: number[]): Promise { + const result = await this.prisma.category.deleteMany({ + where: { hiboutikId: { notIn: keptHiboutikIds } }, + }); + return result.count; + } +} diff --git a/src/database/customer.database.repository.ts b/src/database/customer.database.repository.ts new file mode 100644 index 0000000..8529b78 --- /dev/null +++ b/src/database/customer.database.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { Customer } from '../../prisma/generated/client'; +import { CustomerCreateInput } from '../../prisma/generated/models/Customer'; + +/** Database access to customers. Rows are matched with Hiboutik by `hiboutikId`. */ +@Injectable() +export class CustomerDatabaseRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAll(): Promise { + return this.prisma.customer.findMany({ + orderBy: { hiboutikId: 'asc' }, + }); + } + + /** Creates or updates the customer with the same `hiboutikId`. */ + async upsert(customer: CustomerCreateInput): Promise { + return this.prisma.customer.upsert({ + where: { hiboutikId: customer.hiboutikId }, + create: customer, + update: customer, + }); + } + + /** + * Deletes the customers that no longer exist in Hiboutik. + * Their addresses are deleted (onDelete: Cascade), their orders are kept with no customer (onDelete: SetNull). + */ + async deleteMissing(keptHiboutikIds: number[]): Promise { + const result = await this.prisma.customer.deleteMany({ + where: { hiboutikId: { notIn: keptHiboutikIds } }, + }); + return result.count; + } +} diff --git a/src/database/product.database.repository.ts b/src/database/product.database.repository.ts new file mode 100644 index 0000000..f0f6783 --- /dev/null +++ b/src/database/product.database.repository.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { Product } from '../../prisma/generated/client'; +import { ProductCreateInput } from '../../prisma/generated/models/Product'; + +/** Database access to products. Rows are matched with Hiboutik by `hiboutikId`. */ +@Injectable() +export class ProductDatabaseRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAll(): Promise { + return this.prisma.product.findMany({ + orderBy: { hiboutikId: 'asc' }, + }); + } + + /** Creates or updates the product with the same `hiboutikId`. On update, the relations missing from the input are unlinked. */ + async upsert(product: ProductCreateInput): Promise { + return this.prisma.product.upsert({ + where: { hiboutikId: product.hiboutikId }, + create: product, + update: { + ...product, + // an undefined relation is ignored by Prisma on update: explicitly unlink relations removed in Hiboutik + brand: product.brand ?? { disconnect: true }, + category: product.category ?? { disconnect: true }, + supplier: product.supplier ?? { disconnect: true }, + }, + }); + } + + /** + * Deletes products (and their versions, by cascade). + * Order lines keep their own snapshot of the product, their link is set to null. + */ + async deleteAll(productIds: string[]): Promise { + if (productIds.length === 0) return 0; + + const result = await this.prisma.product.deleteMany({ + where: { id: { in: productIds } }, + }); + return result.count; + } +} diff --git a/src/database/productVersion.database.repository.ts b/src/database/productVersion.database.repository.ts new file mode 100644 index 0000000..c9efa52 --- /dev/null +++ b/src/database/productVersion.database.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { ProductVersion } from '../../prisma/generated/client'; +import { ProductVersionCreateInput } from '../../prisma/generated/models/ProductVersion'; + +/** Database access to product versions (one per Hiboutik size), identified by their product and `hiboutikSizeId`. */ +@Injectable() +export class ProductVersionDatabaseRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAll(): Promise { + return this.prisma.productVersion.findMany({ + orderBy: { hiboutikSizeId: 'asc' }, + }); + } + + /** Creates or updates the version of `productId` with the same `hiboutikSizeId`. */ + async upsert(productId: string, productVersion: ProductVersionCreateInput): Promise { + return this.prisma.productVersion.upsert({ + where: { productId_hiboutikSizeId: { productId: productId, hiboutikSizeId: productVersion.hiboutikSizeId } }, + create: productVersion, + update: productVersion, + }); + } + + /** + * Deletes the versions of a product that no longer exist in Hiboutik. + * Order lines keep their own snapshot of the product, their link is set to null. + */ + async deleteMissing(productId: string, keptHiboutikSizeIds: number[]): Promise { + const result = await this.prisma.productVersion.deleteMany({ + where: { productId, hiboutikSizeId: { notIn: keptHiboutikSizeIds } }, + }); + return result.count; + } +} diff --git a/src/database/supplier.database.repository.ts b/src/database/supplier.database.repository.ts new file mode 100644 index 0000000..b13be2c --- /dev/null +++ b/src/database/supplier.database.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { Supplier } from '../../prisma/generated/client'; +import { SupplierCreateInput } from '../../prisma/generated/models/Supplier'; + +/** Database access to suppliers. Rows are matched with Hiboutik by `hiboutikId`. */ +@Injectable() +export class SupplierDatabaseRepository { + constructor(private readonly prisma: PrismaService) {} + + async getAll(): Promise { + return this.prisma.supplier.findMany({ + orderBy: { hiboutikId: 'asc' }, + }); + } + + /** Creates or updates the supplier with the same `hiboutikId`. */ + async upsert(supplier: SupplierCreateInput): Promise { + return this.prisma.supplier.upsert({ + where: { hiboutikId: supplier.hiboutikId }, + create: supplier, + update: supplier, + }); + } + + /** + * Deletes the suppliers that no longer exist in Hiboutik. + * Their products are kept: the relation is set to null (onDelete: SetNull). + */ + async deleteMissing(keptHiboutikIds: number[]): Promise { + const result = await this.prisma.supplier.deleteMany({ + where: { hiboutikId: { notIn: keptHiboutikIds } }, + }); + return result.count; + } +}