add business categories CRUD module
This commit is contained in:
Generated
+2668
File diff suppressed because it is too large
Load Diff
+9
-1
@@ -1,9 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { dataSourceOptions } from './config/typeorm.config';
|
||||
import { BusinessCategoriesModule } from './modules/business-categories/business-categories.module';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
TypeOrmModule.forRoot(dataSourceOptions),
|
||||
BusinessCategoriesModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BusinessAttributeValueEntity } from './business-attribute-value.entity';
|
||||
import { BusinessCategoryEntity } from './business-category.entity';
|
||||
|
||||
export enum AttributeType {
|
||||
BOOLEAN = 'boolean',
|
||||
TEXT = 'text',
|
||||
NUMBER = 'number',
|
||||
SELECT = 'select',
|
||||
}
|
||||
|
||||
@Entity('attribute_definitions')
|
||||
export class AttributeDefinitionEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ length: 120 })
|
||||
title!: string;
|
||||
|
||||
@Column({ length: 120 })
|
||||
key!: string;
|
||||
|
||||
@Column({ type: 'enum', enum: AttributeType })
|
||||
type!: AttributeType;
|
||||
|
||||
@Column({ type: 'simple-array', nullable: true })
|
||||
options!: string[] | null;
|
||||
|
||||
@Column({ default: false })
|
||||
required!: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
isFilterable!: boolean;
|
||||
|
||||
@ManyToOne(() => BusinessCategoryEntity, (category) => category.attributeDefinitions, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'categoryId' })
|
||||
category!: BusinessCategoryEntity;
|
||||
|
||||
@Column('uuid')
|
||||
categoryId!: string;
|
||||
|
||||
@OneToMany(() => BusinessAttributeValueEntity, (value) => value.attributeDefinition)
|
||||
values!: BusinessAttributeValueEntity[];
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BusinessEntity } from './business.entity';
|
||||
import { BusinessServiceEntity } from './business-service.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
export enum BookingStatus {
|
||||
PENDING = 'pending',
|
||||
CONFIRMED = 'confirmed',
|
||||
CANCELLED = 'cancelled',
|
||||
COMPLETED = 'completed',
|
||||
}
|
||||
|
||||
@Entity('bookings')
|
||||
export class BookingEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.bookings, { onDelete: 'RESTRICT' })
|
||||
@JoinColumn({ name: 'customerId' })
|
||||
customer!: UserEntity;
|
||||
|
||||
@Column('uuid')
|
||||
customerId!: string;
|
||||
|
||||
@ManyToOne(() => BusinessEntity, (business) => business.bookings, { onDelete: 'RESTRICT' })
|
||||
@JoinColumn({ name: 'businessId' })
|
||||
business!: BusinessEntity;
|
||||
|
||||
@Column('uuid')
|
||||
businessId!: string;
|
||||
|
||||
@ManyToOne(() => BusinessServiceEntity, (service) => service.bookings, {
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
@JoinColumn({ name: 'businessServiceId' })
|
||||
businessService!: BusinessServiceEntity;
|
||||
|
||||
@Column('uuid')
|
||||
businessServiceId!: string;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
scheduledAt!: Date;
|
||||
|
||||
@Column({ type: 'enum', enum: BookingStatus, default: BookingStatus.PENDING })
|
||||
status!: BookingStatus;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
note!: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AttributeDefinitionEntity } from './attribute-definition.entity';
|
||||
import { BusinessEntity } from './business.entity';
|
||||
|
||||
@Entity('business_attribute_values')
|
||||
@Unique('UQ_business_attribute_value', ['businessId', 'attributeDefinitionId'])
|
||||
export class BusinessAttributeValueEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@ManyToOne(() => BusinessEntity, (business) => business.attributes, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'businessId' })
|
||||
business!: BusinessEntity;
|
||||
|
||||
@Column('uuid')
|
||||
businessId!: string;
|
||||
|
||||
@ManyToOne(() => AttributeDefinitionEntity, (definition) => definition.values, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'attributeDefinitionId' })
|
||||
attributeDefinition!: AttributeDefinitionEntity;
|
||||
|
||||
@Column('uuid')
|
||||
attributeDefinitionId!: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
value!: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { AttributeDefinitionEntity } from './attribute-definition.entity';
|
||||
import { BusinessEntity } from './business.entity';
|
||||
import { ServiceTemplateEntity } from './service-template.entity';
|
||||
|
||||
@Entity('business_categories')
|
||||
export class BusinessCategoryEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ unique: true, length: 100 })
|
||||
slug!: string;
|
||||
|
||||
@Column({ length: 150 })
|
||||
title!: string;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@OneToMany(() => BusinessEntity, (business) => business.category)
|
||||
businesses!: BusinessEntity[];
|
||||
|
||||
@OneToMany(() => ServiceTemplateEntity, (service) => service.category)
|
||||
serviceTemplates!: ServiceTemplateEntity[];
|
||||
|
||||
@OneToMany(() => AttributeDefinitionEntity, (attribute) => attribute.category)
|
||||
attributeDefinitions!: AttributeDefinitionEntity[];
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BookingEntity } from './booking.entity';
|
||||
import { BusinessEntity } from './business.entity';
|
||||
import { ServiceTemplateEntity } from './service-template.entity';
|
||||
|
||||
@Entity('business_services')
|
||||
@Unique('UQ_business_service', ['businessId', 'serviceTemplateId'])
|
||||
export class BusinessServiceEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@ManyToOne(() => BusinessEntity, (business) => business.services, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'businessId' })
|
||||
business!: BusinessEntity;
|
||||
|
||||
@Column('uuid')
|
||||
businessId!: string;
|
||||
|
||||
@ManyToOne(() => ServiceTemplateEntity, (serviceTemplate) => serviceTemplate.businessServices, {
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
@JoinColumn({ name: 'serviceTemplateId' })
|
||||
serviceTemplate!: ServiceTemplateEntity;
|
||||
|
||||
@Column('uuid')
|
||||
serviceTemplateId!: string;
|
||||
|
||||
@Column({ type: 'numeric', precision: 12, scale: 2 })
|
||||
price!: string;
|
||||
|
||||
@Column({ type: 'int', nullable: true })
|
||||
durationMinutes!: number | null;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@OneToMany(() => BookingEntity, (booking) => booking.businessService)
|
||||
bookings!: BookingEntity[];
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BookingEntity } from './booking.entity';
|
||||
import { BusinessAttributeValueEntity } from './business-attribute-value.entity';
|
||||
import { BusinessCategoryEntity } from './business-category.entity';
|
||||
import { BusinessServiceEntity } from './business-service.entity';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@Entity('businesses')
|
||||
export class BusinessEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ length: 150 })
|
||||
title!: string;
|
||||
|
||||
@Column({ unique: true, length: 120 })
|
||||
slug!: string;
|
||||
|
||||
@Column({ nullable: true, length: 500 })
|
||||
description!: string | null;
|
||||
|
||||
@Column({ nullable: true, length: 20 })
|
||||
contactPhone!: string | null;
|
||||
|
||||
@Column({ nullable: true, length: 500 })
|
||||
address!: string | null;
|
||||
|
||||
@Column({ type: 'double precision', nullable: true })
|
||||
latitude!: number | null;
|
||||
|
||||
@Column({ type: 'double precision', nullable: true })
|
||||
longitude!: number | null;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@ManyToOne(() => BusinessCategoryEntity, (category) => category.businesses, {
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
@JoinColumn({ name: 'categoryId' })
|
||||
category!: BusinessCategoryEntity;
|
||||
|
||||
@Column('uuid')
|
||||
categoryId!: string;
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.ownedBusinesses, {
|
||||
nullable: true,
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn({ name: 'ownerId' })
|
||||
owner!: UserEntity | null;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
ownerId!: string | null;
|
||||
|
||||
@OneToMany(() => BusinessServiceEntity, (service) => service.business)
|
||||
services!: BusinessServiceEntity[];
|
||||
|
||||
@OneToMany(() => BusinessAttributeValueEntity, (attr) => attr.business)
|
||||
attributes!: BusinessAttributeValueEntity[];
|
||||
|
||||
@OneToMany(() => BookingEntity, (booking) => booking.business)
|
||||
bookings!: BookingEntity[];
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BusinessCategoryEntity } from './business-category.entity';
|
||||
import { BusinessServiceEntity } from './business-service.entity';
|
||||
|
||||
@Entity('service_templates')
|
||||
export class ServiceTemplateEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ length: 120 })
|
||||
title!: string;
|
||||
|
||||
@Column({ unique: true, length: 120 })
|
||||
slug!: string;
|
||||
|
||||
@Column({ nullable: true, length: 400 })
|
||||
description!: string | null;
|
||||
|
||||
@Column({ type: 'int', default: 30 })
|
||||
defaultDurationMinutes!: number;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@ManyToOne(() => BusinessCategoryEntity, (category) => category.serviceTemplates, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'categoryId' })
|
||||
category!: BusinessCategoryEntity;
|
||||
|
||||
@Column('uuid')
|
||||
categoryId!: string;
|
||||
|
||||
@OneToMany(() => BusinessServiceEntity, (service) => service.serviceTemplate)
|
||||
businessServices!: BusinessServiceEntity[];
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { BookingEntity } from './booking.entity';
|
||||
import { BusinessEntity } from './business.entity';
|
||||
|
||||
export enum UserRole {
|
||||
CUSTOMER = 'customer',
|
||||
BUSINESS_OWNER = 'business_owner',
|
||||
ADMIN = 'admin',
|
||||
}
|
||||
|
||||
@Entity('users')
|
||||
export class UserEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ unique: true, length: 20 })
|
||||
mobile!: string;
|
||||
|
||||
@Column({ nullable: true, length: 150 })
|
||||
fullName!: string | null;
|
||||
|
||||
@Column({ type: 'enum', enum: UserRole, default: UserRole.CUSTOMER })
|
||||
role!: UserRole;
|
||||
|
||||
@Column({ default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@OneToMany(() => BusinessEntity, (business) => business.owner)
|
||||
ownedBusinesses!: BusinessEntity[];
|
||||
|
||||
@OneToMany(() => BookingEntity, (booking) => booking.customer)
|
||||
bookings!: BookingEntity[];
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'reflect-metadata';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { BusinessCategoryEntity } from '../../entities/business-category.entity';
|
||||
import { BusinessCategoriesService } from './business-categories.service';
|
||||
import { CreateBusinessCategoryDto } from './dto/create-business-category.dto';
|
||||
import { UpdateBusinessCategoryDto } from './dto/update-business-category.dto';
|
||||
|
||||
@Controller('business-categories')
|
||||
export class BusinessCategoriesController {
|
||||
constructor(private readonly service: BusinessCategoriesService) {}
|
||||
|
||||
@Get()
|
||||
findAll(): Promise<BusinessCategoryEntity[]> {
|
||||
return this.service.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string): Promise<BusinessCategoryEntity> {
|
||||
return this.service.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Body() payload: CreateBusinessCategoryDto,
|
||||
): Promise<BusinessCategoryEntity> {
|
||||
return this.service.create(payload);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() payload: UpdateBusinessCategoryDto,
|
||||
): Promise<BusinessCategoryEntity> {
|
||||
return this.service.update(id, payload);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string): Promise<{ success: true }> {
|
||||
await this.service.remove(id);
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { BusinessCategoryEntity } from '../../entities/business-category.entity';
|
||||
import { BusinessCategoriesController } from './business-categories.controller';
|
||||
import { BusinessCategoriesService } from './business-categories.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([BusinessCategoryEntity])],
|
||||
controllers: [BusinessCategoriesController],
|
||||
providers: [BusinessCategoriesService],
|
||||
})
|
||||
export class BusinessCategoriesModule {}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BusinessCategoryEntity } from '../../entities/business-category.entity';
|
||||
import { CreateBusinessCategoryDto } from './dto/create-business-category.dto';
|
||||
import { UpdateBusinessCategoryDto } from './dto/update-business-category.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BusinessCategoriesService {
|
||||
constructor(
|
||||
@InjectRepository(BusinessCategoryEntity)
|
||||
private readonly repo: Repository<BusinessCategoryEntity>,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<BusinessCategoryEntity[]> {
|
||||
return this.repo.find({ order: { createdAt: 'DESC' } });
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<BusinessCategoryEntity> {
|
||||
const category = await this.repo.findOne({ where: { id } });
|
||||
if (!category) {
|
||||
throw new NotFoundException('Business category not found');
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
async create(
|
||||
payload: CreateBusinessCategoryDto,
|
||||
): Promise<BusinessCategoryEntity> {
|
||||
const exists = await this.repo.findOne({ where: { slug: payload.slug } });
|
||||
if (exists) {
|
||||
throw new ConflictException('Slug already exists');
|
||||
}
|
||||
|
||||
const entity = this.repo.create({
|
||||
title: payload.title,
|
||||
slug: payload.slug,
|
||||
isActive: payload.isActive ?? true,
|
||||
});
|
||||
return this.repo.save(entity);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
payload: UpdateBusinessCategoryDto,
|
||||
): Promise<BusinessCategoryEntity> {
|
||||
const category = await this.findOne(id);
|
||||
|
||||
if (payload.slug && payload.slug !== category.slug) {
|
||||
const slugTaken = await this.repo.findOne({ where: { slug: payload.slug } });
|
||||
if (slugTaken) {
|
||||
throw new ConflictException('Slug already exists');
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(category, payload);
|
||||
return this.repo.save(category);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const result = await this.repo.delete(id);
|
||||
if (!result.affected) {
|
||||
throw new NotFoundException('Business category not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class CreateBusinessCategoryDto {
|
||||
title!: string;
|
||||
slug!: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class UpdateBusinessCategoryDto {
|
||||
title?: string;
|
||||
slug?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user