From 62a112d1efc4fd48604d2249cea8a01764bfe0ba Mon Sep 17 00:00:00 2001 From: AnakUtara <141168468+AnakUtara@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:12:09 +0700 Subject: [PATCH] feat(products): create models for products crud --- apps/api/package.json | 3 +- apps/api/prisma/schema.prisma | 32 +++++++++---------- apps/api/src/constants/image.constants.ts | 16 ++++++++++ .../{utils => libs}/prisma/address.args.ts | 0 apps/api/src/libs/prisma/images.args.ts | 18 +++++++++++ .../src/{utils => libs}/prisma/user.args.ts | 0 apps/api/src/middlewares/admin.middleware.ts | 2 +- apps/api/src/models/category.model.ts | 23 +++++++++++++ apps/api/src/models/image.model.ts | 25 +++++++++++++++ apps/api/src/models/products.model.ts | 8 ++++- apps/api/src/models/promo.models.ts | 26 +++++++++++++++ apps/api/src/models/store.model.ts | 32 +++++++++++++++++++ apps/api/src/services/admin.auth.service.ts | 2 +- apps/api/src/services/categories.service.ts | 3 ++ apps/api/src/services/super-admin.services.ts | 2 +- apps/api/src/utils/generate.ts | 18 +++++++++++ package-lock.json | 19 +++++++++++ 17 files changed, 208 insertions(+), 21 deletions(-) create mode 100644 apps/api/src/constants/image.constants.ts rename apps/api/src/{utils => libs}/prisma/address.args.ts (100%) create mode 100644 apps/api/src/libs/prisma/images.args.ts rename apps/api/src/{utils => libs}/prisma/user.args.ts (100%) create mode 100644 apps/api/src/models/category.model.ts create mode 100644 apps/api/src/models/image.model.ts create mode 100644 apps/api/src/models/promo.models.ts create mode 100644 apps/api/src/services/categories.service.ts create mode 100644 apps/api/src/utils/generate.ts diff --git a/apps/api/package.json b/apps/api/package.json index fbc4671..abc0323 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -17,7 +17,6 @@ "license": "ISC", "dependencies": { "@prisma/client": "^5.16.1", - "nodemon": "^3.1.4", "axios": "^1.7.2", "bcrypt": "^5.1.1", "cors": "^2.8.5", @@ -29,8 +28,10 @@ "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "mustache": "^4.2.0", + "nanoid": "^5.0.7", "node-cron": "^3.0.3", "nodemailer": "^6.9.13", + "nodemon": "^3.1.4", "sharp": "^0.33.4", "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 71229bf..2e86062 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -47,10 +47,10 @@ model User { voucher Promotion? @relation(fields: [voucher_id], references: [id]) is_banned Boolean @default(false) customer_orders CustomerOrders[] - Cart Cart[] + cart Cart[] + order_detail OrderDetail[] created_at DateTime @default(now()) updated_at DateTime @updatedAt - OrderDetail OrderDetail[] @@index([id, email, role, is_verified]) @@map("users") @@ -141,7 +141,7 @@ model StoreStock { promo Promotion? @relation(fields: [promo_id], references: [id]) quantity Int order_details OrderDetail[] - Cart Cart[] + cart Cart[] created_at DateTime @default(now()) updated_at DateTime @updatedAt @@ -174,15 +174,15 @@ enum ImageType { } model Image { - id String @id @default(cuid()) - name String @unique - blob Bytes @db.MediumBlob - type ImageType @default(product) - user User? - ProductVariants ProductVariants? - category Category? - created_at DateTime @default(now()) - updated_at DateTime @updatedAt + id String @id @default(cuid()) + name String @unique + blob Bytes @db.MediumBlob + type ImageType @default(product) + user User? + product_variants ProductVariants? + category Category? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt @@index([type]) @@map("images") @@ -275,9 +275,9 @@ model Promotion { min_transaction Float @db.Double expiry_date DateTime is_valid Boolean - StoreStock StoreStock[] - CustomerOrders CustomerOrders[] - User User[] + store_stock StoreStock[] + customer_orders CustomerOrders[] + user User[] created_at DateTime @default(now()) updated_at DateTime @updatedAt @@ -321,7 +321,7 @@ model CustomerOrders { promotion Promotion? @relation(fields: [promotion_id], references: [id]) discount Int? user_id String? - User User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id]) origin_id String origin Store @relation(fields: [origin_id], references: [address_id]) order_details OrderDetail[] diff --git a/apps/api/src/constants/image.constants.ts b/apps/api/src/constants/image.constants.ts new file mode 100644 index 0000000..59dbb1e --- /dev/null +++ b/apps/api/src/constants/image.constants.ts @@ -0,0 +1,16 @@ +import { generateSlug } from '@/utils/generate'; +import { Prisma } from '@prisma/client'; +import sharp from 'sharp'; + +export const imageCreateInput = async ( + file: Express.Multer.File, + id: string, +): Promise => { + const blob = await sharp(file.buffer).webp().toBuffer(); + const name = `${generateSlug(file.fieldname)}-${id}`; + return { + name, + blob, + type: 'avatar', + }; +}; diff --git a/apps/api/src/utils/prisma/address.args.ts b/apps/api/src/libs/prisma/address.args.ts similarity index 100% rename from apps/api/src/utils/prisma/address.args.ts rename to apps/api/src/libs/prisma/address.args.ts diff --git a/apps/api/src/libs/prisma/images.args.ts b/apps/api/src/libs/prisma/images.args.ts new file mode 100644 index 0000000..548649c --- /dev/null +++ b/apps/api/src/libs/prisma/images.args.ts @@ -0,0 +1,18 @@ +import { generateSlug } from '@/utils/generate'; +import { ImageType, Prisma } from '@prisma/client'; +import { Request } from 'express'; +import sharp from 'sharp'; + +export async function imageCreate( + req: Request, + id: string, + mimetype: 'png' | 'jpeg' | 'webp' | 'gif', + type: ImageType, +): Promise { + const file = req.file as Express.Multer.File; + return { + name: `${generateSlug(file.fieldname)}-${id}`, + blob: await sharp(file.buffer)[`${mimetype}`]().toBuffer(), + type, + }; +} diff --git a/apps/api/src/utils/prisma/user.args.ts b/apps/api/src/libs/prisma/user.args.ts similarity index 100% rename from apps/api/src/utils/prisma/user.args.ts rename to apps/api/src/libs/prisma/user.args.ts diff --git a/apps/api/src/middlewares/admin.middleware.ts b/apps/api/src/middlewares/admin.middleware.ts index 1ac127c..7b1b458 100644 --- a/apps/api/src/middlewares/admin.middleware.ts +++ b/apps/api/src/middlewares/admin.middleware.ts @@ -10,7 +10,7 @@ import { TAddress } from '@/models/address.model'; import { TUser } from '@/models/user.model'; import prisma from '@/prisma'; import { AuthError, BadRequestError, InvalidDataError } from '@/utils/error'; -import { adminFindFirst } from '@/utils/prisma/user.args'; +import { adminFindFirst } from '@/libs/prisma/user.args'; import { reqBodyReducer } from '@/utils/req.body.helper'; import { Role } from '@prisma/client'; import { compare } from 'bcrypt'; diff --git a/apps/api/src/models/category.model.ts b/apps/api/src/models/category.model.ts new file mode 100644 index 0000000..510f4f1 --- /dev/null +++ b/apps/api/src/models/category.model.ts @@ -0,0 +1,23 @@ +import { TImage } from './image.model'; +import { TProduct } from './products.model'; + +export type TCategory = { + id: number; + name: string; + image_id?: string; + image?: TImage; + product: TProduct[]; + sub_categories: TSubCategory[]; + created_at?: Date; + updated_at?: Date; +}; + +export type TSubCategory = { + id?: number; + name: string; + category_id: number; + category: TCategory; + products: TProduct[]; + created_at?: Date; + updated_at?: Date; +}; diff --git a/apps/api/src/models/image.model.ts b/apps/api/src/models/image.model.ts new file mode 100644 index 0000000..1f47955 --- /dev/null +++ b/apps/api/src/models/image.model.ts @@ -0,0 +1,25 @@ +import { TCategory } from './category.model'; +import { TVariant } from './products.model'; +import { TUser } from './user.model'; + +export enum ImageType { + avatar = 'avatar', + store = 'store', + product = 'product', + promotion = 'promotion', + discount = 'discount', + voucher = 'voucher', + category = 'category', +} + +export type TImage = { + id?: string; + name?: string; + blob: Buffer; + type: ImageType; + user?: TUser; + ProductVariants: TVariant; + category?: TCategory; + created_at?: Date; + updated_at?: Date; +}; diff --git a/apps/api/src/models/products.model.ts b/apps/api/src/models/products.model.ts index 4dd22e8..8018c07 100644 --- a/apps/api/src/models/products.model.ts +++ b/apps/api/src/models/products.model.ts @@ -1,3 +1,7 @@ +import { TCategory, TSubCategory } from './category.model'; +import { TImage } from './image.model'; +import { TStoreStock } from './store.model'; + export type TProduct = { id?: string; name: string; @@ -7,7 +11,9 @@ export type TProduct = { storage_instructions?: string; category_id: number; sub_category_id: number; - variants: TVariant[]; + category?: TCategory; + sub_category?: TSubCategory; + variants?: TVariant[]; created_at?: Date; updated_at?: Date; }; diff --git a/apps/api/src/models/promo.models.ts b/apps/api/src/models/promo.models.ts new file mode 100644 index 0000000..b723756 --- /dev/null +++ b/apps/api/src/models/promo.models.ts @@ -0,0 +1,26 @@ +import { TStoreStock } from './store.model'; +import { TUser } from './user.model'; + +export enum PromoType { + discount = 'discount', + voucher = 'voucher', + referral_voucher = 'referral_voucher', + cashback = 'cashback', + free_shipping = 'free_shipping', +} + +export type TPromotion = { + id?: string; + title: string; + description?: string; + type: PromoType; + amount: number; + min_transaction: number; + expiry_date: Date; + is_valid: Boolean; + store_stock: TStoreStock[]; + // CustomerOrders CustomerOrders[] + user?: TUser[]; + created_at?: Date; + updated_at?: Date; +}; diff --git a/apps/api/src/models/store.model.ts b/apps/api/src/models/store.model.ts index e69de29..af7a8f9 100644 --- a/apps/api/src/models/store.model.ts +++ b/apps/api/src/models/store.model.ts @@ -0,0 +1,32 @@ +import { TAddress } from './address.model'; +import { TVariant } from './products.model'; +import { TPromotion } from './promo.models'; +import { TUser } from './user.model'; + +export type TStore = { + address_id: string; + address: TAddress; + store_admin: TUser[]; + product_stock: TStoreStock[]; + schedule_id?: string; + // schedule?: TStoreSchedule + // customer_orders: TCustomerOrders[] + created_at?: Date; + updated_at?: Date; +}; + +export type TStoreStock = { + id: string; + store_id: string; + store: TStore; + variant_id: string; + product: TVariant; + // stock_history: TStockHistory[]; + unit_price: number; + discount?: number; + promo_id?: string; + promo?: TPromotion; + quantity: number; + created_at?: Date; + updated_at?: Date; +}; diff --git a/apps/api/src/services/admin.auth.service.ts b/apps/api/src/services/admin.auth.service.ts index 37036f8..36c66da 100644 --- a/apps/api/src/services/admin.auth.service.ts +++ b/apps/api/src/services/admin.auth.service.ts @@ -3,7 +3,7 @@ import { createToken } from '@/libs/jwt'; import { TUser } from '@/models/user.model'; import prisma from '@/prisma'; import { catchAllErrors } from '@/utils/error'; -import { adminOmit } from '@/utils/prisma/user.args'; +import { adminOmit } from '@/libs/prisma/user.args'; import { Request } from 'express'; class AdminAuthService { diff --git a/apps/api/src/services/categories.service.ts b/apps/api/src/services/categories.service.ts new file mode 100644 index 0000000..25f40bc --- /dev/null +++ b/apps/api/src/services/categories.service.ts @@ -0,0 +1,3 @@ +class CategoryService {} + +export default new CategoryService(); diff --git a/apps/api/src/services/super-admin.services.ts b/apps/api/src/services/super-admin.services.ts index 8a2d303..3b05447 100644 --- a/apps/api/src/services/super-admin.services.ts +++ b/apps/api/src/services/super-admin.services.ts @@ -2,7 +2,7 @@ import { TUser } from '@/models/user.model'; import prisma from '@/prisma'; import { catchAllErrors, NotFoundError } from '@/utils/error'; import { countTotalPage, paginate } from '@/utils/pagination'; -import { userFindMany } from '@/utils/prisma/user.args'; +import { userFindMany } from '@/libs/prisma/user.args'; import { Address, AddressType, Role, User } from '@prisma/client'; import { Request } from 'express'; diff --git a/apps/api/src/utils/generate.ts b/apps/api/src/utils/generate.ts new file mode 100644 index 0000000..de87480 --- /dev/null +++ b/apps/api/src/utils/generate.ts @@ -0,0 +1,18 @@ +import { generate } from 'voucher-code-generator'; + +export const generateReferral = () => { + const referral = generate({ + pattern: '####-####', + count: 1, + charset: 'alphanumeric', + }); + + return referral[0]; +}; + +export const generateSlug = (str: string): string => { + return str + .toLocaleLowerCase() + .replace(/ /g, '-') + .replace(/[^\w-]+/g, ''); +}; diff --git a/package-lock.json b/package-lock.json index dc23051..b463cc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "mustache": "^4.2.0", + "nanoid": "^5.0.7", "node-cron": "^3.0.3", "nodemailer": "^6.9.13", "nodemon": "^3.1.4", @@ -63,6 +64,24 @@ "tsx": "^4.16.2" } }, + "apps/api/node_modules/nanoid": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "apps/web": { "version": "0.1.0", "dependencies": {