Merge pull request #7 from MrSedan/dev

Added auth and settings modules
This commit is contained in:
2024-01-02 12:55:29 +03:00
committed by GitHub
18 changed files with 359 additions and 8 deletions

View File

@@ -5,3 +5,5 @@ DATABASE_HOST=localhost
DATABASE_PORT=5432 DATABASE_PORT=5432
SERVER_PORT=3000 SERVER_PORT=3000
ACCESS_TOKEN=123

View File

@@ -16,5 +16,6 @@ export const config = {
}, },
server: { server: {
port: +process.env.SERVER_PORT || 8080, port: +process.env.SERVER_PORT || 8080,
access_token: process.env.ACCESS_TOKEN || '',
}, },
}; };

View File

@@ -0,0 +1,20 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class BotSettings {
constructor(props?: Partial<BotSettings>) {
Object.assign(this, props);
}
@PrimaryGeneratedColumn('uuid')
public uuid!: string;
@Column({ type: 'text', array: true })
public messageTimes!: string[];
@Column()
public channel!: string;
@Column({ default: false })
public isActive!: boolean;
}

View File

@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './database/user.entity';
import { Admin } from './database/admin.entity'; import { Admin } from './database/admin.entity';
import { Post } from './database/post.entity';
import { Image } from './database/image.entity'; import { Image } from './database/image.entity';
import { Payment } from './database/payment.entity'; import { Payment } from './database/payment.entity';
import { Post } from './database/post.entity';
import { ProxyUser } from './database/proxy_user.entity'; import { ProxyUser } from './database/proxy_user.entity';
import { BotSettings } from './database/settings.entity';
import { User } from './database/user.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([User, Admin, Post, Image, Payment, ProxyUser])], imports: [TypeOrmModule.forFeature([User, Admin, Post, Image, Payment, ProxyUser, BotSettings])],
exports: [TypeOrmModule], exports: [TypeOrmModule],
}) })
export class LibsModule {} export class LibsModule {}

View File

@@ -22,10 +22,13 @@
"dependencies": { "dependencies": {
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.1.16", "@nestjs/swagger": "^7.1.16",
"@nestjs/typeorm": "^10.0.1", "@nestjs/typeorm": "^10.0.1",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"passport": "^0.7.0",
"passport-http-bearer": "^1.0.1",
"pg": "^8.11.3", "pg": "^8.11.3",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
@@ -38,6 +41,7 @@
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jest": "^29.5.2", "@types/jest": "^29.5.2",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/passport-http-bearer": "^1.0.41",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",

107
backend/pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ dependencies:
'@nestjs/core': '@nestjs/core':
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.2.8(@nestjs/common@10.2.8)(@nestjs/platform-express@10.2.8)(reflect-metadata@0.1.13)(rxjs@7.8.1) version: 10.2.8(@nestjs/common@10.2.8)(@nestjs/platform-express@10.2.8)(reflect-metadata@0.1.13)(rxjs@7.8.1)
'@nestjs/passport':
specifier: ^10.0.3
version: 10.0.3(@nestjs/common@10.2.8)(passport@0.7.0)
'@nestjs/platform-express': '@nestjs/platform-express':
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.2.8(@nestjs/common@10.2.8)(@nestjs/core@10.2.8) version: 10.2.8(@nestjs/common@10.2.8)(@nestjs/core@10.2.8)
@@ -23,6 +26,12 @@ dependencies:
dotenv: dotenv:
specifier: ^16.3.1 specifier: ^16.3.1
version: 16.3.1 version: 16.3.1
passport:
specifier: ^0.7.0
version: 0.7.0
passport-http-bearer:
specifier: ^1.0.1
version: 1.0.1
pg: pg:
specifier: ^8.11.3 specifier: ^8.11.3
version: 8.11.3 version: 8.11.3
@@ -55,6 +64,9 @@ devDependencies:
'@types/node': '@types/node':
specifier: ^20.3.1 specifier: ^20.3.1
version: 20.9.0 version: 20.9.0
'@types/passport-http-bearer':
specifier: ^1.0.41
version: 1.0.41
'@types/supertest': '@types/supertest':
specifier: ^2.0.12 specifier: ^2.0.12
version: 2.0.16 version: 2.0.16
@@ -964,6 +976,16 @@ packages:
reflect-metadata: 0.1.13 reflect-metadata: 0.1.13
dev: false dev: false
/@nestjs/passport@10.0.3(@nestjs/common@10.2.8)(passport@0.7.0):
resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==}
peerDependencies:
'@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0
passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0
dependencies:
'@nestjs/common': 10.2.8(reflect-metadata@0.1.13)(rxjs@7.8.1)
passport: 0.7.0
dev: false
/@nestjs/platform-express@10.2.8(@nestjs/common@10.2.8)(@nestjs/core@10.2.8): /@nestjs/platform-express@10.2.8(@nestjs/common@10.2.8)(@nestjs/core@10.2.8):
resolution: {integrity: sha512-WoSSVtwIRc5AdGMHWVzWZK4JZLT0f4o2xW8P9gQvcX+omL8W1kXCfY8GQYXNBG84XmBNYH8r0FtC8oMe/lH5NQ==} resolution: {integrity: sha512-WoSSVtwIRc5AdGMHWVzWZK4JZLT0f4o2xW8P9gQvcX+omL8W1kXCfY8GQYXNBG84XmBNYH8r0FtC8oMe/lH5NQ==}
peerDependencies: peerDependencies:
@@ -1141,6 +1163,12 @@ packages:
/@tsconfig/node16@1.0.4: /@tsconfig/node16@1.0.4:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
/@types/accepts@1.3.7:
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
dependencies:
'@types/node': 20.9.0
dev: true
/@types/babel__core@7.20.4: /@types/babel__core@7.20.4:
resolution: {integrity: sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==} resolution: {integrity: sha512-mLnSC22IC4vcWiuObSRjrLd9XcBTGf59vUSoq2jkQDJ/QQ8PMI9rSuzE+aEV8karUMbskw07bKYoUJCKTUaygg==}
dependencies: dependencies:
@@ -1183,10 +1211,23 @@ packages:
'@types/node': 20.9.0 '@types/node': 20.9.0
dev: true dev: true
/@types/content-disposition@0.5.8:
resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==}
dev: true
/@types/cookiejar@2.1.4: /@types/cookiejar@2.1.4:
resolution: {integrity: sha512-b698BLJ6kPVd6uhHsY7wlebZdrWPXYied883PDSzpJZYOP97EOn/oGdLCH3jJf157srkFReIZY5v0H1s8Dozrg==} resolution: {integrity: sha512-b698BLJ6kPVd6uhHsY7wlebZdrWPXYied883PDSzpJZYOP97EOn/oGdLCH3jJf157srkFReIZY5v0H1s8Dozrg==}
dev: true dev: true
/@types/cookies@0.7.10:
resolution: {integrity: sha512-hmUCjAk2fwZVPPkkPBcI7jGLIR5mg4OVoNMBwU6aVsMm/iNPY7z9/R+x2fSwLt/ZXoGua6C5Zy2k5xOo9jUyhQ==}
dependencies:
'@types/connect': 3.4.38
'@types/express': 4.17.21
'@types/keygrip': 1.0.6
'@types/node': 20.9.0
dev: true
/@types/eslint-scope@3.7.7: /@types/eslint-scope@3.7.7:
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
dependencies: dependencies:
@@ -1229,6 +1270,10 @@ packages:
'@types/node': 20.9.0 '@types/node': 20.9.0
dev: true dev: true
/@types/http-assert@1.5.5:
resolution: {integrity: sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==}
dev: true
/@types/http-errors@2.0.4: /@types/http-errors@2.0.4:
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
dev: true dev: true
@@ -1260,6 +1305,29 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true dev: true
/@types/keygrip@1.0.6:
resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==}
dev: true
/@types/koa-compose@3.2.8:
resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==}
dependencies:
'@types/koa': 2.13.12
dev: true
/@types/koa@2.13.12:
resolution: {integrity: sha512-vAo1KuDSYWFDB4Cs80CHvfmzSQWeUb909aQib0C0aFx4sw0K9UZFz2m5jaEP+b3X1+yr904iQiruS0hXi31jbw==}
dependencies:
'@types/accepts': 1.3.7
'@types/content-disposition': 0.5.8
'@types/cookies': 0.7.10
'@types/http-assert': 1.5.5
'@types/http-errors': 2.0.4
'@types/keygrip': 1.0.6
'@types/koa-compose': 3.2.8
'@types/node': 20.9.0
dev: true
/@types/mime@1.3.5: /@types/mime@1.3.5:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
dev: true dev: true
@@ -1273,6 +1341,20 @@ packages:
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
/@types/passport-http-bearer@1.0.41:
resolution: {integrity: sha512-ecW+9e8C+0id5iz3YZ+uIarsk/vaRPkKSajt1i1Am66t0mC9gDfQDKXZz9fnPOW2xKUufbmCSou4005VM94Feg==}
dependencies:
'@types/express': 4.17.21
'@types/koa': 2.13.12
'@types/passport': 1.0.16
dev: true
/@types/passport@1.0.16:
resolution: {integrity: sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==}
dependencies:
'@types/express': 4.17.21
dev: true
/@types/qs@6.9.10: /@types/qs@6.9.10:
resolution: {integrity: sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==} resolution: {integrity: sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==}
dev: true dev: true
@@ -4354,6 +4436,27 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
/passport-http-bearer@1.0.1:
resolution: {integrity: sha512-SELQM+dOTuMigr9yu8Wo4Fm3ciFfkMq5h/ZQ8ffi4ELgZrX1xh9PlglqZdcUZ1upzJD/whVyt+YWF62s3U6Ipw==}
engines: {node: '>= 0.4.0'}
dependencies:
passport-strategy: 1.0.0
dev: false
/passport-strategy@1.0.0:
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
engines: {node: '>= 0.4.0'}
dev: false
/passport@0.7.0:
resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==}
engines: {node: '>= 0.4.0'}
dependencies:
passport-strategy: 1.0.0
pause: 0.0.1
utils-merge: 1.0.1
dev: false
/path-exists@4.0.0: /path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -4397,6 +4500,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
dev: false
/pg-cloudflare@1.1.1: /pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
requiresBuild: true requiresBuild: true

View File

@@ -4,23 +4,30 @@ import { config } from 'config';
import { LibsModule } from 'libs/libs.module'; import { LibsModule } from 'libs/libs.module';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AdminModule } from './modules/admin/admin.module'; import { AdminModule } from './modules/admin/admin.module';
import { AuthModule } from './modules/auth/auth.module';
import { ImageModule } from './modules/image/image.module'; import { ImageModule } from './modules/image/image.module';
import { AppInitService } from './modules/initialization/app.init.service'; import { AppInitService } from './modules/initialization/app.init.service';
import { PostModule } from './modules/post/post.module'; import { PostModule } from './modules/post/post.module';
import { ProxyModule } from './modules/proxy/proxy.module'; import { ProxyModule } from './modules/proxy/proxy.module';
import { SettingsModule } from './modules/settings/settings.module';
import { UserModule } from './modules/user/user.module'; import { UserModule } from './modules/user/user.module';
@Module({ @Module({
imports: [ imports: [
AuthModule,
LibsModule, LibsModule,
PostModule, PostModule,
AdminModule, AdminModule,
UserModule, UserModule,
ImageModule, ImageModule,
ProxyModule, ProxyModule,
SettingsModule,
TypeOrmModule.forRoot(<TypeOrmModuleOptions>config.database), TypeOrmModule.forRoot(<TypeOrmModuleOptions>config.database),
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppInitService], providers: [
AppInitService,
// { provide: APP_GUARD, useClass: AuthGuard }, // Если будет необходима авторизация
],
}) })
export class AppModule {} export class AppModule {}

View File

@@ -11,8 +11,12 @@ export class AdminService {
async getAdmins() { async getAdmins() {
try { try {
this.logger.debug(`[admin.getAdmins]`); this.logger.debug(`[admin.getAdmins]`);
const admins = await this.adminRepository.find(); const admins = await this.adminRepository.find({ relations: { user: true }, select: { user_id: true, user: { user_name: true } } });
return admins; const result: { user_id: string; user_name: string }[] = admins.map((admin) => ({
user_id: admin.user_id,
user_name: admin.user.user_name,
}));
return result;
} catch (error) { } catch (error) {
this.logger.log(`[getAdmin] ${JSON.stringify({ error })}`); this.logger.log(`[getAdmin] ${JSON.stringify({ error })}`);
return []; return [];

View File

@@ -0,0 +1,36 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private reflector: Reflector,
private readonly authService: AuthService,
) {}
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const allowUnauthorizedRequest = this.reflector.get<boolean>('allowUnauthorizedRequest', context.getHandler());
let token = this.extractTokenFromHeader(request.headers);
if (!token) {
token = request.query.access_token || request.body.access_token;
}
if (allowUnauthorizedRequest || this.authService.authUserByToken(token)) return true;
throw new UnauthorizedException('Unathorized!');
}
private extractTokenFromHeader(headers: any): string | null {
if (headers && headers.authorization) {
const authHeader = headers.authorization as string;
const headerParts = authHeader.split(' ');
if (headerParts.length === 2 && headerParts[0].toLowerCase() === 'bearer') {
return headerParts[1];
}
}
return null;
}
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { HttpBearerStrategy } from './http-bearer.strategy';
@Module({
imports: [PassportModule.register({ defaultStrategy: 'bearer' })],
providers: [HttpBearerStrategy, AuthService],
exports: [HttpBearerStrategy, AuthService],
})
export class AuthModule {}

View File

@@ -0,0 +1,10 @@
import { Injectable, Logger } from '@nestjs/common';
import { config } from 'config';
@Injectable()
export class AuthService {
private readonly logger: Logger = new Logger(AuthService.name);
authUserByToken(token: string) {
return token === config.server.access_token;
}
}

View File

@@ -0,0 +1,18 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-http-bearer';
import { AuthService } from './auth.service';
@Injectable()
export class HttpBearerStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(token: string): Promise<boolean> {
const user = await this.authService.authUserByToken(token);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}

View File

@@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Admin } from 'libs/database/admin.entity'; import { Admin } from 'libs/database/admin.entity';
import { Image } from 'libs/database/image.entity'; import { Image } from 'libs/database/image.entity';
import { Post } from 'libs/database/post.entity'; import { Post } from 'libs/database/post.entity';
import { BotSettings } from 'libs/database/settings.entity';
import { User } from 'libs/database/user.entity'; import { User } from 'libs/database/user.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@@ -13,6 +14,7 @@ export class AppInitService implements OnModuleInit {
@InjectRepository(Admin) private adminRepository: Repository<Admin>, @InjectRepository(Admin) private adminRepository: Repository<Admin>,
@InjectRepository(Post) private postRepository: Repository<Post>, @InjectRepository(Post) private postRepository: Repository<Post>,
@InjectRepository(Image) private ImageRepository: Repository<Image>, @InjectRepository(Image) private ImageRepository: Repository<Image>,
@InjectRepository(BotSettings) private BotSettingsRepository: Repository<BotSettings>,
) {} ) {}
async onModuleInit() {} async onModuleInit() {}

View File

@@ -0,0 +1,42 @@
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ICreateBotSettingsProfile, IEditBotSettingsProfile } from './settings.dto';
import { SettingsService } from './settings.service';
// Если нужна будет авторизация, для выключения авторизации на конкретном
// const AllowUnathorizedRequest = () => SetMetadata('allowUnathorizedRequest', true);
@ApiTags('Settings')
@Controller('settings')
export class SettingsController {
constructor(private settingsService: SettingsService) {}
@ApiOperation({ description: 'Get settings for bot' })
@Get()
async getSettings() {
return await this.settingsService.getSettings();
}
@ApiOperation({ description: 'Get all bot settings profiles' })
@Get('profile')
async getProfiles() {
return await this.settingsService.getProfiles();
}
@ApiOperation({ description: 'Create new settings profile' })
@Post('profile/new')
async newProfile(@Body() data: ICreateBotSettingsProfile) {
return await this.settingsService.newProfile(data);
}
@ApiOperation({ description: 'Edit settings profile' })
@Post('profile/edit')
async editProfile(@Body() data: IEditBotSettingsProfile) {
return await this.settingsService.editProfile(data);
}
@ApiOperation({ description: 'Delete settings profile' })
@Delete('profile/delete/:uuid')
async deleteProfile(@Param('uuid') uuid: string) {
return await this.settingsService.deleteProfile(uuid);
}
}

View File

@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
export class ICreateBotSettingsProfile {
@ApiProperty({ description: 'Channel', example: '@neurowoman_test' }) readonly channel: string;
@ApiProperty({ description: 'Post times', example: ['12:00', '14:00'] }) readonly postTimes: string[];
}
export class IEditBotSettingsProfile {
@ApiProperty({ description: 'Channel', example: '@neurowoman_test' }) readonly channel: string | undefined;
@ApiProperty({ description: 'Profile uuid', example: '96e7d1b3-20ca-46e8-ac68-e406f79b7eb9' }) readonly uuid: string;
@ApiProperty({ description: 'Post times', example: ['12:00', '14:00'] }) readonly postTimes: string[] | undefined;
@ApiProperty({ description: 'Is active?', example: false }) readonly isActive: boolean | undefined;
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { LibsModule } from 'libs/libs.module';
import { SettingsController } from './settings.controller';
import { SettingsService } from './settings.service';
@Module({
imports: [LibsModule],
controllers: [SettingsController],
providers: [SettingsService],
})
export class SettingsModule {}

View File

@@ -0,0 +1,62 @@
import { HttpException, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { BotSettings } from 'libs/database/settings.entity';
import { Repository } from 'typeorm';
import { ICreateBotSettingsProfile, IEditBotSettingsProfile } from './settings.dto';
@Injectable()
export class SettingsService {
constructor(@InjectRepository(BotSettings) private botSettingsRepository: Repository<BotSettings>) {}
private readonly logger: Logger = new Logger(SettingsService.name);
async getSettings() {
this.logger.log('[settings.getSettings]');
const settings = await this.botSettingsRepository.findOneBy({ isActive: true });
if (settings) return settings;
this.logger.debug(`[settings.getSettings] No active settings found`);
throw new HttpException('No settings found', 404);
}
async newProfile(data: ICreateBotSettingsProfile) {
this.logger.log(`[settings.newProfile] data: ${JSON.stringify(data)}`);
return await this.botSettingsRepository.save({ channel: data.channel, messageTimes: data.postTimes });
}
async getProfiles() {
this.logger.log(`[settings.getProfiles]`);
return await this.botSettingsRepository.find();
}
async editProfile(data: IEditBotSettingsProfile) {
this.logger.log(`[settings.editProfile] data: ${JSON.stringify(data)}`);
const editProfile = await this.botSettingsRepository.findOneBy({ uuid: data.uuid });
if (!editProfile) {
this.logger.debug(`[settings.editProfile] No profile found`);
throw new HttpException('No profile found', 404);
}
data.channel ? (editProfile.channel = data.channel) : '';
data.postTimes ? (editProfile.messageTimes = data.postTimes) : '';
if (data.isActive !== undefined) {
editProfile.isActive = data.isActive;
if (data.isActive) {
const nowActive = await this.botSettingsRepository.findOneBy({ isActive: true });
nowActive.isActive = false;
await this.botSettingsRepository.save(nowActive);
}
}
return await this.botSettingsRepository.save(editProfile);
}
async deleteProfile(profile_uuid: string) {
this.logger.log(`[settings.deleteProfile] uuid: ${profile_uuid}`);
const deleteProfile = await this.botSettingsRepository.findOneBy({ uuid: profile_uuid });
if (!deleteProfile) {
this.logger.debug(`[settings.deleteProfile] No profile found`);
throw new HttpException('No profile found', 404);
}
if (deleteProfile.isActive) {
this.logger.debug(`[settings.deleteProfile] Can't delete active profile`);
throw new HttpException("Can't delete active profile", 400);
}
return await this.botSettingsRepository.delete(deleteProfile.uuid);
}
}