import {
HttpException,
Logger,
MiddlewareConsumer,
Module,
NestModule,
OnModuleInit,
} from '@nestjs/common';
import { UserModule } from './user/user.module';
import { DbModule } from '@app/db';
import { PermissionModule } from './permission/permission.module';
import { AuthModule } from './auth/auth.module';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth/auth.guard';
import { PermissionGuard } from './permission/permission.guard';
import { RoleModule } from './role/role.module';
import { join } from 'path';
import { readFileSync } from 'fs';
import { MenuModule } from './menu/menu.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { I18Module } from './i18/i18.module';
import { I18LangService } from './i18/lang.service';
import { I18Service } from './i18/i18.service';
import {
HeaderResolver,
I18nModule,
} from 'nestjs-i18n';
import { MockModule } from './mock/mock.module';
import { RejectRequestGuard } from './public/reject.guard';
import { HealthCheckController } from './health-check.controller';
import { ApplicationModule } from './application/application.module';
import { CONFIG_SCHEMA, Configure } from './config-schema';
import { InstallLock } from './install-lock';
import { RedisService } from '../libs/redis/redis.service';
import Redis from 'ioredis';
import { RedisModule } from '../libs/redis/redis.module';
import { LockerModule,LockerDiscover, LockRequestContextMiddleware } from '@app/locker';
import { MenuInitializer } from './menu/menu.initializer';
import { RoleInit } from './role/role.initializer';
import { PermissionInit } from './permission/permission.initalizer';
import { UserInit } from './user/user.initalizer';
import { ApplicationInit } from './application/application.init';
const INSTALL_FLAG = 'FLAG:INSTALL';
const MAX_RETRY = 20;
@Module({
imports: [
DbModule,
UserModule,
PermissionModule,
AuthModule,
RoleModule,
MenuModule,
ApplicationModule,
ConfigModule.forRoot({
isGlobal: true,
expandVariables: true,
validationSchema: CONFIG_SCHEMA
}),
I18Module,
I18nModule.forRoot({
fallbackLanguage: 'enUS',
loaderOptions: {
path: join(__dirname, '/i18n/'),
watch: true,
},
resolvers: [new HeaderResolver(['x-lang'])],
typesOutputPath: join(__dirname, '../src/.generate/i18n.generated.ts'),
}),
MockModule,
RedisModule,
LockerModule.register({
global: true
}),
],
controllers: [HealthCheckController],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: APP_GUARD,
useClass: RejectRequestGuard,
},
{
provide: APP_GUARD,
useClass: PermissionGuard,
},
InstallLock,
LockerDiscover,
],
exports: [LockerDiscover]
})
export class AppModule implements OnModuleInit,NestModule {
constructor(
private lang: I18LangService,
private i18: I18Service,
private lock: InstallLock,
private redis: RedisService,
private cfg: ConfigService<Configure>,
private menuInit: MenuInitializer,
private roleInit: RoleInit,
private permissionInit: PermissionInit,
private userInit: UserInit,
private applicationInit: ApplicationInit
) {}
async isInstalled(redis: Redis) {
return redis.exists(INSTALL_FLAG);
}
async setIsInstalled(redis: Redis){
return redis.set(INSTALL_FLAG, '1');
}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LockRequestContextMiddleware)
.forRoutes('*');
}
async onModuleInit(
) {
const IS_PREVIEW_MOD = this.cfg.get('PREVIEW_MODE');
if (IS_PREVIEW_MOD) {
Logger.warn('You are currently in demonstration mode. All additions, deletions, and modifications request will be rejected');
Logger.warn('If you want to disable the demo mode, please set the `PREVIEW_MODE` environment variable to `false`')
Logger.warn('Alternatively, you can create an `.env` file and set `PREVIEV_MODE` to `false`')
}
const redis = this.redis.getRedis();
if (await this.isInstalled(redis)){
Logger.warn(
`Lock file exists, if you want init again, please remove ${INSTALL_FLAG} in your redis`
);
return;
}
if (await this.lock.exist()){
if (await this.lock.wait(MAX_RETRY)) {
if (await this.isInstalled(redis)) {
return;
}
Logger.error('Install fail! Please restart application');
process.exit(-1);
}
Logger.error('Wait timeout. Please restart your application');
process.exit(-1);
}
const lockStatus = await this.lock.lock(60 * 5);
if (!lockStatus) {
const waitStatus = await this.lock.wait(MAX_RETRY);
if (!waitStatus) {
Logger.error('Wait timeout. Please restart your application');
process.exit(-1);
}
if (await this.isInstalled(redis)){
return;
}
Logger.error('Install fail! Please clear database and restart application');
process.exit(-1);
}
if (await this.isInstalled(redis)) {
Logger.log('Already installed');
await this.lock.release();
return;
}
await this.lock.extend(60 * 5);
try {
const I18_INIT_FILE_PATH = join(process.cwd(), 'locales.json');
const I18_INIT_FILE = JSON.parse(
readFileSync(I18_INIT_FILE_PATH).toString()
);
const dbLangNames = (await this.lang.findAll()).map((lang) => lang.name);
const langs = Object.keys(I18_INIT_FILE).filter(
(key) => !dbLangNames.includes(key)
);
for (const name of langs) {
const { id } = await this.lang.create({ name });
for (const [key, value] of Object.entries(I18_INIT_FILE[name])) {
const dbValue = await this.i18.has(key, id);
if (dbValue) {
Logger.warn(`${name} - ${key} exists value is ${dbValue.content}`);
continue;
}
Logger.log(`${name} - ${key} not exists`);
await this.i18.create({ key, content: value as string, lang: id });
Logger.log(`${name} - ${key} save success`);
}
}
await this.applicationInit.run();
await this.menuInit.run();
await this.permissionInit.run();
await this.roleInit.run();
await this.userInit.run()
await this.setIsInstalled(redis);
} catch (e) {
const err = e as HttpException;
Logger.error(err.message);
Logger.error(`Please clear the database and try again`);
await this.lock.release();
process.exit(-1);
} finally {
await this.lock.release();
}
}
}