feat: add language crud

This commit is contained in:
Mohamed Marrouchi
2024-09-22 12:51:56 +01:00
parent 614766c246
commit 10f36c2d48
25 changed files with 849 additions and 48 deletions

View File

@@ -0,0 +1,151 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import {
BadRequestException,
Body,
Controller,
Delete,
Get,
HttpCode,
NotFoundException,
Param,
Patch,
Post,
Query,
UseInterceptors,
} from '@nestjs/common';
import { CsrfCheck } from '@tekuconcept/nestjs-csrf';
import { TFilterQuery } from 'mongoose';
import { CsrfInterceptor } from '@/interceptors/csrf.interceptor';
import { LoggerService } from '@/logger/logger.service';
import { BaseController } from '@/utils/generics/base-controller';
import { DeleteResult } from '@/utils/generics/base-repository';
import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
import { PageQueryPipe } from '@/utils/pagination/pagination-query.pipe';
import { SearchFilterPipe } from '@/utils/pipes/search-filter.pipe';
import { LanguageCreateDto, LanguageUpdateDto } from '../dto/language.dto';
import { Language } from '../schemas/language.schema';
import { LanguageService } from '../services/language.service';
@UseInterceptors(CsrfInterceptor)
@Controller('language')
export class LanguageController extends BaseController<Language> {
constructor(
private readonly languageService: LanguageService,
private readonly logger: LoggerService,
) {
super(languageService);
}
/**
* Retrieves a paginated list of categories based on provided filters and pagination settings.
* @param pageQuery - The pagination settings.
* @param filters - The filters to apply to the language search.
* @returns A Promise that resolves to a paginated list of categories.
*/
@Get()
async findPage(
@Query(PageQueryPipe) pageQuery: PageQueryDto<Language>,
@Query(new SearchFilterPipe<Language>({ allowedFields: ['title', 'code'] }))
filters: TFilterQuery<Language>,
) {
return await this.languageService.findPage(filters, pageQuery);
}
/**
* Counts the filtered number of categories.
* @returns A promise that resolves to an object representing the filtered number of categories.
*/
@Get('count')
async filterCount(
@Query(
new SearchFilterPipe<Language>({
allowedFields: ['title', 'code'],
}),
)
filters?: TFilterQuery<Language>,
) {
return await this.count(filters);
}
/**
* Finds a language by its ID.
* @param id - The ID of the language to find.
* @returns A Promise that resolves to the found language.
*/
@Get(':id')
async findOne(@Param('id') id: string): Promise<Language> {
const doc = await this.languageService.findOne(id);
if (!doc) {
this.logger.warn(`Unable to find Language by id ${id}`);
throw new NotFoundException(`Language with ID ${id} not found`);
}
return doc;
}
/**
* Creates a new language.
* @param language - The data of the language to be created.
* @returns A Promise that resolves to the created language.
*/
@CsrfCheck(true)
@Post()
async create(@Body() language: LanguageCreateDto): Promise<Language> {
return await this.languageService.create(language);
}
/**
* Updates an existing language.
* @param id - The ID of the language to be updated.
* @param languageUpdate - The updated data for the language.
* @returns A Promise that resolves to the updated language.
*/
@CsrfCheck(true)
@Patch(':id')
async updateOne(
@Param('id') id: string,
@Body() languageUpdate: LanguageUpdateDto,
): Promise<Language> {
if ('default' in languageUpdate) {
if (languageUpdate.default) {
// A new default language is define, make sure that only one is marked as default
await this.languageService.updateMany({}, { default: false });
} else {
throw new BadRequestException('Should not be able to disable default');
}
}
const result = await this.languageService.updateOne(id, languageUpdate);
if (!result) {
this.logger.warn(`Unable to update Language by id ${id}`);
throw new NotFoundException(`Language with ID ${id} not found`);
}
return result;
}
/**
* Deletes a language by its ID.
* @param id - The ID of the language to be deleted.
* @returns A Promise that resolves to the deletion result.
*/
@CsrfCheck(true)
@Delete(':id')
@HttpCode(204)
async deleteOne(@Param('id') id: string): Promise<DeleteResult> {
const result = await this.languageService.deleteOne(id);
if (result.deletedCount === 0) {
this.logger.warn(`Unable to delete Language by id ${id}`);
throw new NotFoundException(`Language with ID ${id} not found`);
}
return result;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { PartialType } from '@nestjs/mapped-types';
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
export class LanguageCreateDto {
@ApiProperty({ description: 'Language Title', type: String })
@IsNotEmpty()
@IsString()
title: string;
@ApiProperty({ description: 'Language Code', type: String })
@IsNotEmpty()
@IsString()
code: string;
@ApiProperty({ description: 'Is Default Language ?', type: Boolean })
@IsNotEmpty()
@IsBoolean()
default: boolean;
}
export class LanguageUpdateDto extends PartialType(LanguageCreateDto) {}

View File

@@ -7,23 +7,36 @@
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { DynamicModule, Global, Inject, Module } from '@nestjs/common';
import {
DynamicModule,
forwardRef,
Global,
Inject,
Module,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { MongooseModule } from '@nestjs/mongoose';
import {
I18N_OPTIONS,
I18N_TRANSLATIONS,
I18nModule as NativeI18nModule,
I18nOptions,
I18nTranslation,
I18nModule as NativeI18nModule,
} from 'nestjs-i18n';
import { Observable } from 'rxjs';
import { ChatModule } from '@/chat/chat.module';
import { LanguageController } from './controllers/language.controller';
import { TranslationController } from './controllers/translation.controller';
import { LanguageRepository } from './repositories/language.repository';
import { TranslationRepository } from './repositories/translation.repository';
import { LanguageModel } from './schemas/language.schema';
import { TranslationModel } from './schemas/translation.schema';
import { LanguageSeeder } from './seeds/language.seed';
import { TranslationSeeder } from './seeds/translation.seed';
import { I18nService } from './services/i18n.service';
import { LanguageService } from './services/language.service';
import { TranslationService } from './services/translation.service';
@Global()
@@ -43,10 +56,19 @@ export class I18nModule extends NativeI18nModule {
const { imports, providers, controllers, exports } = super.forRoot(options);
return {
module: I18nModule,
imports: imports.concat([MongooseModule.forFeature([TranslationModel])]),
controllers: controllers.concat([TranslationController]),
imports: (imports || []).concat([
MongooseModule.forFeature([LanguageModel, TranslationModel]),
forwardRef(() => ChatModule),
]),
controllers: (controllers || []).concat([
LanguageController,
TranslationController,
]),
providers: providers.concat([
I18nService,
LanguageRepository,
LanguageService,
LanguageSeeder,
TranslationRepository,
TranslationService,
TranslationSeeder,

View File

@@ -0,0 +1,23 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { BaseRepository } from '@/utils/generics/base-repository';
import { Language } from '../schemas/language.schema';
@Injectable()
export class LanguageRepository extends BaseRepository<Language> {
constructor(@InjectModel(Language.name) readonly model: Model<Language>) {
super(model, Language);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { Prop, Schema, SchemaFactory, ModelDefinition } from '@nestjs/mongoose';
import { THydratedDocument } from 'mongoose';
import { BaseSchema } from '@/utils/generics/base-schema';
@Schema({ timestamps: true })
export class Language extends BaseSchema {
@Prop({
type: String,
required: true,
unique: true,
})
title: string;
@Prop({
type: String,
required: true,
unique: true,
})
code: string;
@Prop({
type: Boolean,
})
default: boolean;
}
export const LanguageModel: ModelDefinition = {
name: Language.name,
schema: SchemaFactory.createForClass(Language),
};
export type LanguageDocument = THydratedDocument<Language>;
export default LanguageModel.schema;

View File

@@ -0,0 +1,23 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { LanguageCreateDto } from '../dto/language.dto';
export const languageModels: LanguageCreateDto[] = [
{
title: 'English',
code: 'en',
default: true,
},
{
title: 'Français',
code: 'fr',
default: false,
},
];

View File

@@ -0,0 +1,22 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { Injectable } from '@nestjs/common';
import { BaseSeeder } from '@/utils/generics/base-seeder';
import { LanguageRepository } from '../repositories/language.repository';
import { Language } from '../schemas/language.schema';
@Injectable()
export class LanguageSeeder extends BaseSeeder<Language> {
constructor(private readonly languageRepository: LanguageRepository) {
super(languageRepository);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import { Injectable } from '@nestjs/common';
import { BaseService } from '@/utils/generics/base-service';
import { LanguageRepository } from '../repositories/language.repository';
import { Language } from '../schemas/language.schema';
@Injectable()
export class LanguageService extends BaseService<Language> {
constructor(readonly repository: LanguageRepository) {
super(repository);
}
}

View File

@@ -13,6 +13,8 @@ import { CategorySeeder } from './chat/seeds/category.seed';
import { categoryModels } from './chat/seeds/category.seed-model';
import { ContextVarSeeder } from './chat/seeds/context-var.seed';
import { contextVarModels } from './chat/seeds/context-var.seed-model';
import { LanguageSeeder } from './i18n/seeds/language.seed';
import { languageModels } from './i18n/seeds/language.seed-model';
import { TranslationSeeder } from './i18n/seeds/translation.seed';
import { translationModels } from './i18n/seeds/translation.seed-model';
import { LoggerService } from './logger/logger.service';
@@ -40,6 +42,7 @@ export async function seedDatabase(app: INestApplicationContext) {
const settingSeeder = app.get(SettingSeeder);
const permissionSeeder = app.get(PermissionSeeder);
const userSeeder = app.get(UserSeeder);
const languageSeeder = app.get(LanguageSeeder);
const translationSeeder = app.get(TranslationSeeder);
const nlpEntitySeeder = app.get(NlpEntitySeeder);
const nlpValueSeeder = app.get(NlpValueSeeder);
@@ -127,6 +130,14 @@ export async function seedDatabase(app: INestApplicationContext) {
throw e;
}
// Seed languages
try {
await languageSeeder.seed(languageModels);
} catch (e) {
logger.error('Unable to seed the database with languages!');
throw e;
}
// Seed translations
try {
await translationSeeder.seed(translationModels);

View File

@@ -100,6 +100,11 @@ export const modelModels: ModelCreateDto[] = [
identity: 'subscriber',
attributes: {},
},
{
name: 'Language',
identity: 'language',
attributes: {},
},
{
name: 'Translation',
identity: 'translation',

View File

@@ -26,6 +26,7 @@ export type TModel =
| 'conversation'
| 'message'
| 'subscriber'
| 'language'
| 'translation'
| 'botstats'
| 'menu'