chore: add business logic

This commit is contained in:
Mahyargdz
2025-05-18 10:19:00 +03:30
parent 479c9c390a
commit 9eeab529a9
25 changed files with 350 additions and 74 deletions
-2
View File
@@ -49,7 +49,6 @@
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-fastify": "^11.1.0",
"@nestjs/swagger": "^11.2.0",
"@nestjs/throttler": "^6.4.0",
@@ -80,7 +79,6 @@
"@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7",
"@types/bcrypt": "^5.0.2",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.7",
"@types/nodemailer": "^6.4.17",
+15 -10
View File
@@ -65,9 +65,6 @@ importers:
'@nestjs/passport':
specifier: ^11.0.5
version: 11.0.5(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
'@nestjs/platform-express':
specifier: ^11.0.1
version: 11.1.0(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)
'@nestjs/platform-fastify':
specifier: ^11.1.0
version: 11.1.0(@fastify/static@8.1.1)(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)
@@ -153,9 +150,6 @@ importers:
'@types/bcrypt':
specifier: ^5.0.2
version: 5.0.2
'@types/express':
specifier: ^5.0.0
version: 5.0.1
'@types/jest':
specifier: ^29.5.14
version: 29.5.14
@@ -8048,6 +8042,7 @@ snapshots:
tslib: 2.8.1
transitivePeerDependencies:
- supports-color
optional: true
'@nestjs/platform-fastify@11.1.0(@fastify/static@8.1.1)(@nestjs/common@11.1.0(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.0)':
dependencies:
@@ -9423,6 +9418,7 @@ snapshots:
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
optional: true
bytes@3.1.2: {}
@@ -9642,6 +9638,7 @@ snapshots:
inherits: 2.0.4
readable-stream: 2.3.8
typedarray: 0.0.6
optional: true
concat-stream@2.0.0:
dependencies:
@@ -11165,7 +11162,8 @@ snapshots:
is-docker: 2.2.1
optional: true
isarray@1.0.0: {}
isarray@1.0.0:
optional: true
isarray@2.0.5: {}
@@ -12302,6 +12300,7 @@ snapshots:
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
optional: true
mkdirp@1.0.4: {}
@@ -12334,6 +12333,7 @@ snapshots:
object-assign: 4.1.1
type-is: 1.6.18
xtend: 4.0.2
optional: true
mute-stream@2.0.0: {}
@@ -12776,7 +12776,8 @@ snapshots:
uuid: 9.0.1
optional: true
process-nextick-args@2.0.1: {}
process-nextick-args@2.0.1:
optional: true
process-warning@4.0.1: {}
@@ -12930,6 +12931,7 @@ snapshots:
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
optional: true
readable-stream@3.6.2:
dependencies:
@@ -13066,7 +13068,8 @@ snapshots:
has-symbols: 1.1.0
isarray: 2.0.5
safe-buffer@5.1.2: {}
safe-buffer@5.1.2:
optional: true
safe-buffer@5.2.1: {}
@@ -13290,7 +13293,8 @@ snapshots:
statuses@2.0.1: {}
streamsearch@1.1.0: {}
streamsearch@1.1.0:
optional: true
streamx@2.22.0:
dependencies:
@@ -13350,6 +13354,7 @@ snapshots:
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
optional: true
string_decoder@1.3.0:
dependencies:
+9
View File
@@ -15,6 +15,7 @@ import { databaseConfig } from "./configs/mikro-orm.config";
import { rateLimitConfig } from "./configs/rateLimit.config";
import { HTTPLogger } from "./core/middlewares/logger.middleware";
import { AuthModule } from "./modules/auth/auth.module";
import { BusinessesModule } from "./modules/businesses/businesses.module";
import { CompaniesModule } from "./modules/companies/companies.module";
import { IndustriesModule } from "./modules/industries/industries.module";
import { InvoicesModule } from "./modules/invoices/invoices.module";
@@ -23,6 +24,7 @@ import { TicketsModule } from "./modules/tickets/tickets.module";
import { UploaderModule } from "./modules/uploader/uploader.module";
import { UsersModule } from "./modules/users/users.module";
import { UtilsModule } from "./modules/utils/utils.module";
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, cache: true }),
@@ -50,7 +52,14 @@ import { UtilsModule } from "./modules/utils/utils.module";
UploaderModule,
TicketsModule,
InvoicesModule,
BusinessesModule,
],
// providers: [
// {
// provide: APP_INTERCEPTOR,
// useClass: BusinessInterceptor,
// },
// ],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
@@ -0,0 +1,17 @@
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
import { FastifyRequest } from "fastify";
/**
* Extracts the business ID from the request header
* @example
* ```typescript
* @Get()
* findAll(@BusinessIdHeader() businessId: string) {
* // Use businessId
* }
* ```
*/
export const BusinessIdHeader = createParamDecorator((headerName: string = "X-Business-Id", ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest<FastifyRequest>();
return req.headers[headerName.toLocaleLowerCase()];
});
@@ -0,0 +1,11 @@
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
import { FastifyRequest } from "fastify";
import { Business } from "../../modules/businesses/entities/business.entity";
export const BusinessDec = createParamDecorator((data: keyof Business | undefined, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest<FastifyRequest>();
const business = req.business;
return data ? business?.[data] : business;
});
+5
View File
@@ -597,3 +597,8 @@ export const enum CompanyMessage {
COMPANY_ID_REQUIRED = "شرکت مورد نیاز است",
COMPANY_ID_SHOULD_BE_A_UUID = "شناسه شرکت باید یک UUID معتبر باشد",
}
export const enum BusinessMessage {
NOT_FOUND = "کسب و کار یافت نشد",
BUSINESS_ID_REQUIRED = "شناسه کسب و کار مورد نیاز است",
}
+1 -1
View File
@@ -10,7 +10,7 @@ export function bullMqConfig(): SharedBullAsyncConfiguration {
},
prefix: "dzone",
defaultJobOptions: {
removeOnComplete: false,
removeOnComplete: true,
removeOnFail: false,
attempts: 5,
},
@@ -0,0 +1,50 @@
import {
BadRequestException,
CallHandler,
ExecutionContext,
ForbiddenException,
Injectable,
Logger,
NestInterceptor,
} from "@nestjs/common";
import { FastifyRequest } from "fastify";
import { Observable } from "rxjs";
import { BusinessMessage } from "../../common/enums/message.enum";
import { Business } from "../../modules/businesses/entities/business.entity";
import { BusinessService } from "../../modules/businesses/services/businesses.service";
declare module "fastify" {
interface FastifyRequest {
business?: Business;
}
}
@Injectable()
export class BusinessInterceptor implements NestInterceptor {
private readonly headerName = "x-business-id";
private readonly logger = new Logger(BusinessInterceptor.name);
constructor(private readonly businessService: BusinessService) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest<FastifyRequest>();
const businessId = request.headers[this.headerName.toLocaleLowerCase()] as string;
if (businessId) {
try {
const business = await this.businessService.getBusinessByDanakSubscriptionId(businessId);
request.business = business;
} catch (error) {
this.logger.error(error);
throw new BadRequestException(BusinessMessage.NOT_FOUND);
}
} else {
throw new ForbiddenException(BusinessMessage.BUSINESS_ID_REQUIRED);
}
return next.handle();
}
}
@@ -0,0 +1,21 @@
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { BullModule } from "@nestjs/bullmq";
import { Module } from "@nestjs/common";
import { SUBSCRIPTIONS } from "./constant";
import { Business } from "./entities/business.entity";
import { BusinessProvisioningProcessor } from "./queue/business-provisioning.processor";
import { BusinessService } from "./services/businesses.service";
@Module({
imports: [
MikroOrmModule.forFeature([Business]),
BullModule.registerQueue({
name: SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME,
prefix: SUBSCRIPTIONS.PROVISIONING_QUEUE_PREFIX,
}),
],
providers: [BusinessService, BusinessProvisioningProcessor],
exports: [BusinessService],
})
export class BusinessesModule {}
+9
View File
@@ -0,0 +1,9 @@
export const SUBSCRIPTIONS = Object.freeze({
PROVISIONING_QUEUE_PREFIX: "provisioning",
PROVISIONING_QUEUE_NAME: "provisioning",
PROVISIONING_JOB_NAME: "business.created",
//
SERVICE_SLUG: "Dzonea",
SERVICE_ID: "7e3c2f08-b7e7-4402-ae5f-fea99cff56bd",
});
@@ -0,0 +1,27 @@
import { Cascade, Collection, Entity, EntityRepositoryType, OneToMany, Property, Unique } from "@mikro-orm/core";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Company } from "../../companies/entities/company.entity";
import { Industry } from "../../industries/entities/industry.entity";
import { BusinessRepository } from "../repositories/business.repository";
@Entity({ repository: () => BusinessRepository })
export class Business extends BaseEntity {
@Unique()
@Property({ type: "uuid", nullable: false })
danakSubscriptionId!: string;
@Property({ type: "varchar", length: 255, nullable: false })
name!: string;
@Property({ type: "varchar", length: 255, nullable: true })
logoUrl?: string;
@OneToMany(() => Company, (company) => company.business, { cascade: [Cascade.ALL] })
companies = new Collection<Company>(this);
@OneToMany(() => Industry, (industry) => industry.business)
industries = new Collection<Industry>(this);
[EntityRepositoryType]?: BusinessRepository;
}
@@ -0,0 +1,8 @@
export interface IBusinessProvisioningJob {
subscriptionId: string;
serviceId: string;
serviceName: string;
businessName: string;
userId: string;
slug: string;
}
@@ -0,0 +1,54 @@
// src/business/queue/business-provisioning.processor.ts
import { EntityManager } from "@mikro-orm/postgresql";
import { Processor } from "@nestjs/bullmq";
import { Logger } from "@nestjs/common";
import { Job } from "bullmq";
import { WorkerProcessor } from "../../../common/queues/worker.processor";
import { SUBSCRIPTIONS } from "../constant";
import { Business } from "../entities/business.entity";
import { IBusinessProvisioningJob } from "../interfaces/IBusiness";
@Processor(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME)
export class BusinessProvisioningProcessor extends WorkerProcessor {
protected readonly logger = new Logger(BusinessProvisioningProcessor.name);
constructor(private readonly em: EntityManager) {
super();
}
async process(job: Job) {
switch (job.name) {
case SUBSCRIPTIONS.PROVISIONING_JOB_NAME:
await this.handleBusinessCreated(job);
break;
default:
this.logger.warn(`Unknown job name: ${job.name}`);
}
}
private async handleBusinessCreated(job: Job<IBusinessProvisioningJob>) {
const em = this.em.fork();
const { subscriptionId, serviceId, serviceName, businessName } = job.data;
// Only act if the event is for this service
if (serviceId !== SUBSCRIPTIONS.SERVICE_ID) {
this.logger.debug(`Skipping job for service: ${serviceName}`);
return;
}
// Check if already exists
let business = await em.findOne(Business, { danakSubscriptionId: subscriptionId });
if (!business) {
business = em.create(Business, {
danakSubscriptionId: subscriptionId,
name: businessName,
// logoUrl: job.data.logoUrl, // Uncomment if logoUrl is provided in job data
});
await em.persistAndFlush(business);
this.logger.log(`Created business for subscription ${subscriptionId}`);
} else {
this.logger.debug(`Business already exists for subscription ${subscriptionId}`);
}
}
}
@@ -0,0 +1,5 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { Business } from "../entities/business.entity";
export class BusinessRepository extends EntityRepository<Business> {}
@@ -0,0 +1,16 @@
import { BadRequestException, Injectable } from "@nestjs/common";
import { BusinessMessage } from "../../../common/enums/message.enum";
import { BusinessRepository } from "../repositories/business.repository";
@Injectable()
export class BusinessService {
constructor(private readonly businessRepository: BusinessRepository) {}
async getBusinessByDanakSubscriptionId(danakSubscriptionId: string) {
const business = await this.businessRepository.findOne({ danakSubscriptionId, deletedAt: null });
if (!business) throw new BadRequestException(BusinessMessage.NOT_FOUND);
return business;
}
}
+17 -13
View File
@@ -1,48 +1,52 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
import { CompanyListQueryDto } from "./DTO/company-list-query.dto";
import { CreateCompanyDto } from "./DTO/create-company.dto";
import { UpdateCompanyDto } from "./DTO/update-company.dto";
import { CompaniesService } from "./services/companies.service";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity";
@Controller("companies")
@UseInterceptors(BusinessInterceptor)
export class CompaniesController {
constructor(private readonly companiesService: CompaniesService) {}
@Post()
@ApiOperation({ summary: "create company (admin)" })
create(@Body() createCompanyDto: CreateCompanyDto) {
return this.companiesService.create(createCompanyDto);
create(@Body() createCompanyDto: CreateCompanyDto, @BusinessDec() business: Business) {
return this.companiesService.create(createCompanyDto, business);
}
@Patch(":id")
@ApiOperation({ summary: "update company (admin)" })
update(@Param() paramDto: ParamDto, @Body() updateCompanyDto: UpdateCompanyDto) {
return this.companiesService.updateCompanyById(paramDto.id, updateCompanyDto);
update(@Param() paramDto: ParamDto, @Body() updateCompanyDto: UpdateCompanyDto, @BusinessDec("id") businessId: string) {
return this.companiesService.updateCompanyById(paramDto.id, updateCompanyDto, businessId);
}
@Delete(":id")
@ApiOperation({ summary: "delete company (admin)" })
delete(@Param() paramDto: ParamDto) {
return this.companiesService.deleteCompanyById(paramDto.id);
delete(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.deleteCompanyById(paramDto.id, businessId);
}
@Get("list")
@ApiOperation({ summary: "get companies list (admin)" })
getCompaniesList(@Query() query: CompanyListQueryDto) {
return this.companiesService.getCompaniesList(query);
getCompaniesList(@Query() query: CompanyListQueryDto, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompaniesList(query, businessId);
}
@Get(":id")
@ApiOperation({ summary: "get company by id" })
get(@Param() paramDto: ParamDto) {
return this.companiesService.getCompanyById(paramDto.id);
get(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.getCompanyById(paramDto.id, businessId);
}
@Patch(":id/toggle-status")
@ApiOperation({ summary: "toggle company status (admin)" })
toggleStatus(@Param() paramDto: ParamDto) {
return this.companiesService.toggleStatus(paramDto.id);
toggleStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.companiesService.toggleStatus(paramDto.id, businessId);
}
}
+2 -1
View File
@@ -6,11 +6,12 @@ import { CompanyProduct } from "./entities/company-product.entity";
import { CompanyService } from "./entities/company-service.entity";
import { Company } from "./entities/company.entity";
import { CompaniesService } from "./services/companies.service";
import { BusinessesModule } from "../businesses/businesses.module";
import { Industry } from "../industries/entities/industry.entity";
import { UsersModule } from "../users/users.module";
import { UtilsModule } from "../utils/utils.module";
@Module({
imports: [MikroOrmModule.forFeature([Company, Industry, CompanyProduct, CompanyService]), UsersModule, UtilsModule],
imports: [MikroOrmModule.forFeature([Company, Industry, CompanyProduct, CompanyService]), UsersModule, UtilsModule, BusinessesModule],
providers: [CompaniesService],
controllers: [CompaniesController],
exports: [CompaniesService],
@@ -3,6 +3,7 @@ import { Cascade, Collection, Entity, EntityRepositoryType, Enum, Formula, ManyT
import { CompanyProduct } from "./company-product.entity";
import { CompanyService } from "./company-service.entity";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { Industry } from "../../industries/entities/industry.entity";
import { Invoice } from "../../invoices/entities/invoice.entity";
import { User } from "../../users/entities/user.entity";
@@ -66,5 +67,8 @@ export class Company extends BaseEntity {
@ManyToOne(() => User, { deleteRule: "restrict" })
user!: User;
@ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business;
[EntityRepositoryType]?: CompanyRepository;
}
@@ -6,11 +6,12 @@ import { Company } from "../entities/company.entity";
export class CompanyRepository extends EntityRepository<Company> {
//
async getCompaniesListForAdmin(queryDto: CompanyListQueryDto) {
async getCompaniesListForAdmin(queryDto: CompanyListQueryDto, businessId: string) {
const { limit, skip } = PaginationUtils(queryDto);
const where: FilterQuery<Company> = {
deletedAt: null,
business: { id: businessId },
};
if (queryDto.status) {
@@ -3,6 +3,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import dayjs from "dayjs";
import { CompanyMessage, IndustryMessage } from "../../../common/enums/message.enum";
import { Business } from "../../businesses/entities/business.entity";
import { Industry } from "../../industries/entities/industry.entity";
import { User } from "../../users/entities/user.entity";
import { UsersService } from "../../users/services/users.service";
@@ -25,7 +26,7 @@ export class CompaniesService {
private readonly em: EntityManager,
) {}
async create(createDto: CreateCompanyDto) {
async create(createDto: CreateCompanyDto, business: Business) {
const em = this.em.fork();
try {
await em.begin();
@@ -34,8 +35,8 @@ export class CompaniesService {
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
const [existingCompany, existIdentityNumber] = await Promise.all([
em.findOne(Company, { name: createDto.name }),
em.findOne(Company, { identificationNumber: createDto.identificationNumber }),
em.findOne(Company, { name: createDto.name, business: { id: business.id } }),
em.findOne(Company, { identificationNumber: createDto.identificationNumber, business: { id: business.id } }),
]);
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
@@ -65,6 +66,7 @@ export class CompaniesService {
products,
services,
user,
business,
});
await em.persistAndFlush(company);
@@ -82,14 +84,18 @@ export class CompaniesService {
//-----------------------------------
async updateCompanyById(id: string, updateDto: UpdateCompanyDto) {
async updateCompanyById(id: string, updateDto: UpdateCompanyDto, businessId: string) {
const em = this.em.fork();
try {
await em.begin();
const { services, products, ...rest } = updateDto;
const company = await em.findOne(Company, { id, deletedAt: null }, { populate: ["user", "services", "products"] });
const company = await em.findOne(
Company,
{ id, deletedAt: null, business: { id: businessId } },
{ populate: ["user", "services", "products"] },
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
const updatedCompany = em.assign(company, rest);
@@ -101,8 +107,14 @@ export class CompaniesService {
}
if (updateDto.name) {
const existingCompany = await em.findOne(Company, { name: updateDto.name, deletedAt: null, id: { $ne: company.id } });
const existingCompany = await em.findOne(Company, {
name: updateDto.name,
deletedAt: null,
id: { $ne: company.id },
business: { id: businessId },
});
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_NAME);
updatedCompany.name = updateDto.name;
}
if (updateDto.identificationNumber) {
@@ -110,8 +122,10 @@ export class CompaniesService {
identificationNumber: updateDto.identificationNumber,
deletedAt: null,
id: { $ne: company.id },
business: { id: businessId },
});
if (existingCompany) throw new BadRequestException(CompanyMessage.COMPANY_ALREADY_EXISTS_IDENTITY_NUMBER);
updatedCompany.identificationNumber = updateDto.identificationNumber;
}
const userUpdateData: Partial<User> = {};
@@ -174,15 +188,15 @@ export class CompaniesService {
//-----------------------------------
async getCompanyById(id: string) {
const company = await this.findCompanyById(id);
async getCompanyById(id: string, businessId: string) {
const company = await this.findCompanyById(id, businessId);
return { company };
}
//-----------------------------------
async getCompaniesList(queryDto: CompanyListQueryDto) {
const [companies, count] = await this.companyRepository.getCompaniesListForAdmin(queryDto);
async getCompaniesList(queryDto: CompanyListQueryDto, businessId: string) {
const [companies, count] = await this.companyRepository.getCompaniesListForAdmin(queryDto, businessId);
return {
companies,
count,
@@ -192,8 +206,8 @@ export class CompaniesService {
//-----------------------------------
async deleteCompanyById(id: string) {
const company = await this.findCompanyById(id);
async deleteCompanyById(id: string, businessId: string) {
const company = await this.findCompanyById(id, businessId);
company.deletedAt = dayjs().toDate();
@@ -205,8 +219,8 @@ export class CompaniesService {
//-----------------------------------
async toggleStatus(id: string) {
const company = await this.findCompanyById(id);
async toggleStatus(id: string, businessId: string) {
const company = await this.findCompanyById(id, businessId);
company.isActive = !company.isActive;
@@ -231,9 +245,9 @@ export class CompaniesService {
return company;
}
//-----------------------------------
private async findCompanyById(id: string) {
private async findCompanyById(id: string, businessId: string) {
const company = await this.companyRepository.findOne(
{ id, deletedAt: null },
{ id, deletedAt: null, business: { id: businessId } },
{ populate: ["user", "industry", "services", "products"] },
);
if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND);
@@ -1,6 +1,7 @@
import { Collection, Entity, EntityRepositoryType, Formula, OneToMany, Opt, Property } from "@mikro-orm/core";
import { Collection, Entity, EntityRepositoryType, Formula, ManyToOne, OneToMany, Opt, Property } from "@mikro-orm/core";
import { BaseEntity } from "../../../common/entities/base.entity";
import { Business } from "../../businesses/entities/business.entity";
import { Company } from "../../companies/entities/company.entity";
import { IndustryRepository } from "../repositories/industry.repository";
@@ -21,5 +22,8 @@ export class Industry extends BaseEntity {
@OneToMany(() => Company, (company) => company.industry)
companies = new Collection<Company>(this);
@ManyToOne(() => Business, { deleteRule: "restrict" })
business!: Business;
[EntityRepositoryType]?: IndustryRepository;
}
+17 -13
View File
@@ -1,49 +1,53 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseInterceptors } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
import { CreateIndustryDto } from "./DTO/create-industry.dto";
import { IndustryListQueryDto } from "./DTO/industry-list-query.dto";
import { UpdateIndustryDto } from "./DTO/update-industry.dto";
import { IndustriesService } from "./services/industries.service";
import { BusinessDec } from "../../common/decorators/business.decorator";
import { ParamDto } from "../../common/DTO/param.dto";
import { BusinessInterceptor } from "../../core/interceptors/business.interceptor";
import { Business } from "../businesses/entities/business.entity";
@Controller("industries")
@UseInterceptors(BusinessInterceptor)
export class IndustriesController {
constructor(private readonly industriesService: IndustriesService) {}
@Post()
@ApiOperation({ summary: "create industry" })
create(@Body() createIndustryDto: CreateIndustryDto) {
return this.industriesService.create(createIndustryDto);
create(@Body() createIndustryDto: CreateIndustryDto, @BusinessDec() business: Business) {
return this.industriesService.create(createIndustryDto, business);
}
@Get(":id")
@ApiOperation({ summary: "get industry by id" })
getIndustryById(@Param() paramDto: ParamDto) {
return this.industriesService.getIndustriesById(paramDto.id);
getIndustryById(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.industriesService.getIndustriesById(paramDto.id, businessId);
}
@Patch(":id")
@ApiOperation({ summary: "update industry" })
updateIndustry(@Param() paramDto: ParamDto, @Body() updateIndustryDto: UpdateIndustryDto) {
return this.industriesService.updateIndustry(paramDto.id, updateIndustryDto);
updateIndustry(@Param() paramDto: ParamDto, @Body() updateIndustryDto: UpdateIndustryDto, @BusinessDec("id") businessId: string) {
return this.industriesService.updateIndustry(paramDto.id, updateIndustryDto, businessId);
}
@Delete(":id")
@ApiOperation({ summary: "delete industry" })
deleteIndustry(@Param() paramDto: ParamDto) {
return this.industriesService.deleteIndustry(paramDto.id);
deleteIndustry(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.industriesService.deleteIndustry(paramDto.id, businessId);
}
@Get("list")
@ApiOperation({ summary: "get industries list for admin" })
getIndustriesForAdmin(@Query() query: IndustryListQueryDto) {
return this.industriesService.getIndustriesListForAdmin(query);
getIndustriesForAdmin(@Query() query: IndustryListQueryDto, @BusinessDec("id") businessId: string) {
return this.industriesService.getIndustriesListForAdmin(query, businessId);
}
@Patch(":id/toggle-status")
@ApiOperation({ summary: "toggle industry status" })
toggleStatus(@Param() paramDto: ParamDto) {
return this.industriesService.toggleStatus(paramDto.id);
toggleStatus(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) {
return this.industriesService.toggleStatus(paramDto.id, businessId);
}
}
+2 -1
View File
@@ -1,12 +1,13 @@
import { MikroOrmModule } from "@mikro-orm/nestjs";
import { Module } from "@nestjs/common";
import { BusinessesModule } from "../businesses/businesses.module";
import { Industry } from "./entities/industry.entity";
import { IndustriesController } from "./industries.controller";
import { IndustriesService } from "./services/industries.service";
@Module({
imports: [MikroOrmModule.forFeature([Industry])],
imports: [MikroOrmModule.forFeature([Industry]), BusinessesModule],
controllers: [IndustriesController],
providers: [IndustriesService],
exports: [IndustriesService],
@@ -6,10 +6,14 @@ import { Industry } from "../entities/industry.entity";
export class IndustryRepository extends EntityRepository<Industry> {
//
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto) {
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto, businessId: string) {
const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.createQueryBuilder("i").where({ deletedAt: null }).orderBy({ createdAt: "DESC" }).limit(limit).offset(skip);
const queryBuilder = this.createQueryBuilder("i")
.where({ deletedAt: null, business: { id: businessId } })
.orderBy({ createdAt: "DESC" })
.limit(limit)
.offset(skip);
if (queryDto.q) {
queryBuilder.andWhere({ title: { $ilike: `%${queryDto.q}%` } });
@@ -3,6 +3,7 @@ import { BadRequestException, Injectable } from "@nestjs/common";
import dayjs from "dayjs";
import { IndustryMessage } from "../../../common/enums/message.enum";
import { Business } from "../../businesses/entities/business.entity";
import { CreateIndustryDto } from "../DTO/create-industry.dto";
import { IndustryListQueryDto } from "../DTO/industry-list-query.dto";
import { UpdateIndustryDto } from "../DTO/update-industry.dto";
@@ -17,11 +18,14 @@ export class IndustriesService {
//-----------------------------------
async create(createIndustryDto: CreateIndustryDto) {
const existingIndustry = await this.industryRepository.findOne({ title: createIndustryDto.title });
async create(createIndustryDto: CreateIndustryDto, business: Business) {
const existingIndustry = await this.industryRepository.findOne({
title: createIndustryDto.title,
business: { id: business.id },
});
if (existingIndustry) throw new BadRequestException(IndustryMessage.INDUSTRY_ALREADY_EXISTS);
const industry = this.industryRepository.create(createIndustryDto);
const industry = this.industryRepository.create({ ...createIndustryDto, business });
this.em.persistAndFlush(industry);
return {
@@ -31,16 +35,16 @@ export class IndustriesService {
}
//-----------------------------------
async getIndustriesById(id: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null });
async getIndustriesById(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null, business: { id: businessId } });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
return { industry };
}
//-----------------------------------
async updateIndustry(id: string, updateIndustryDto: UpdateIndustryDto) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null });
async updateIndustry(id: string, updateIndustryDto: UpdateIndustryDto, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null, business: { id: businessId } });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
this.industryRepository.assign(industry, updateIndustryDto);
@@ -54,8 +58,8 @@ export class IndustriesService {
//-----------------------------------
async deleteIndustry(id: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null });
async deleteIndustry(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null, business: { id: businessId } });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
industry.deletedAt = dayjs().toDate();
@@ -68,14 +72,14 @@ export class IndustriesService {
}
//-----------------------------------
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto) {
const [industries, count] = await this.industryRepository.getIndustriesListForAdmin(queryDto);
async getIndustriesListForAdmin(queryDto: IndustryListQueryDto, businessId: string) {
const [industries, count] = await this.industryRepository.getIndustriesListForAdmin(queryDto, businessId);
return { industries, count, paginate: true };
}
//-----------------------------------
async toggleStatus(id: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null });
async toggleStatus(id: string, businessId: string) {
const industry = await this.industryRepository.findOne({ id, deletedAt: null, business: { id: businessId } });
if (!industry) throw new BadRequestException(IndustryMessage.INDUSTRY_NOT_FOUND);
industry.isActive = !industry.isActive;