@boarteam/boar-pack-users-backend 5.4.2 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boarteam/boar-pack-users-backend",
3
- "version": "5.4.2",
3
+ "version": "5.6.0",
4
4
  "description": "NestJS Users module including permissions system, authentication strategies etc",
5
5
  "main": "src/index",
6
6
  "files": [
@@ -14,7 +14,7 @@
14
14
  "access": "public"
15
15
  },
16
16
  "dependencies": {
17
- "@boarteam/boar-pack-common-backend": "^2.3.0",
17
+ "@boarteam/boar-pack-common-backend": "^2.3.1",
18
18
  "@casl/ability": "^6.7.0",
19
19
  "@nestjs/common": "^9.0.0",
20
20
  "@nestjs/config": "^3.2.0",
@@ -39,6 +39,7 @@
39
39
  "passport-jwt": "^4.0.1",
40
40
  "passport-local": "^1.0.0",
41
41
  "pg": "^8.13.1",
42
+ "telegraf": "^4.12.2",
42
43
  "typeorm": "^0.3.20",
43
44
  "ws": "^8.16.0"
44
45
  },
@@ -60,5 +61,5 @@
60
61
  "yalc:push": "yalc push",
61
62
  "gen-types": "SWAGGER=true JWT_SECRET=swagger nest start"
62
63
  },
63
- "gitHead": "a679169b74b3397be6ab850474d34fcd95cc281a"
64
+ "gitHead": "e6e2920ef9786ec27cc616c35796f64f6183a3f5"
64
65
  }
@@ -7,6 +7,7 @@ import { Permission } from '../users/entities/permissions';
7
7
  import { EventLog } from '../event-logs';
8
8
  import { Token } from "../tokens/entities/token.entity";
9
9
  import { MyToken } from "../tokens/policies/manage-my-tokens.policy";
10
+ import { Setting } from "../settings/entities/setting.entity";
10
11
 
11
12
  type AnyObject = Record<PropertyKey, unknown>;
12
13
 
@@ -15,6 +16,7 @@ export interface TSubjects {
15
16
  EventLog: typeof EventLog;
16
17
  Token: typeof Token;
17
18
  MyToken: typeof MyToken;
19
+ Setting: typeof Setting;
18
20
  }
19
21
 
20
22
  export type TTextSubjects = 'all';
@@ -12,7 +12,7 @@ import { CONFIGURE_EVENTS_MIDDLEWARE, SERVICE_CONFIG_TOKEN } from "./event-logs.
12
12
  import { TEventLogServiceConfig } from "./event-logs.types";
13
13
  import { EventLogsLogger } from "./event-logs.logger";
14
14
  import { EventLogMiddleware } from "./event-logs.middleware";
15
- import { ScheduleModule } from "@nestjs/schedule";
15
+ import { ScheduleModule } from "@boarteam/boar-pack-common-backend";
16
16
 
17
17
  @Module({})
18
18
  export class EventLogsModule implements NestModule {
@@ -50,7 +50,7 @@ export class EventLogsModule implements NestModule {
50
50
  module: EventLogsModule,
51
51
  imports: [
52
52
  CaslModule.forFeature(),
53
- ScheduleModule.forRoot(),
53
+ ScheduleModule,
54
54
  TypeOrmModule.forFeature([EventLog], config.dataSourceName),
55
55
  ],
56
56
  providers: [
@@ -9,6 +9,9 @@ import { generate } from "openapi-typescript-codegen";
9
9
  import { EventLogsModule } from './event-logs'
10
10
  import { UsersModule } from "./users";
11
11
  import { TokensModule } from "./tokens";
12
+ import { SettingsModule } from "./settings";
13
+ import { TelegrafModule } from "./telegraf";
14
+ import { AuthModule } from "./auth";
12
15
 
13
16
  @Module({
14
17
  imports: [
@@ -16,7 +19,7 @@ import { TokensModule } from "./tokens";
16
19
  envFilePath: resolve(__dirname, '../../.env'),
17
20
  }),
18
21
  TypeOrmModule.forRoot({
19
- name: 'tid_db',
22
+ name: 'boar_pack_db',
20
23
  type: 'postgres',
21
24
  host: 'localhost',
22
25
  port: 5951,
@@ -27,15 +30,30 @@ import { TokensModule } from "./tokens";
27
30
  resolve(__dirname, './*/entities/*.entity.{ts,js}'),
28
31
  ],
29
32
  }),
33
+ AuthModule.forRoot({
34
+ googleAuth: false,
35
+ msAuth: false,
36
+ localAuth: false,
37
+ withControllers: true,
38
+ dataSourceName: 'boar_pack_db',
39
+ }),
30
40
  UsersModule.register({
31
41
  withControllers: true,
32
- dataSourceName: 'tid_db',
42
+ dataSourceName: 'boar_pack_db',
33
43
  }),
34
44
  EventLogsModule.forRoot({
35
- dataSourceName: 'tid_db'
45
+ dataSourceName: 'boar_pack_db'
36
46
  }),
37
47
  TokensModule.forRoot({
38
- dataSourceName: 'tid_db',
48
+ dataSourceName: 'boar_pack_db',
49
+ }),
50
+ SettingsModule.register({
51
+ withControllers: true,
52
+ dataSourceName: 'boar_pack_db',
53
+ }),
54
+ TelegrafModule.register({
55
+ withControllers: true,
56
+ dataSourceName: 'boar_pack_db',
39
57
  }),
40
58
  ],
41
59
  })
package/src/index.ts CHANGED
@@ -6,3 +6,5 @@ export * from './bcrypt';
6
6
  export * from './tokens';
7
7
  export * from './ws-auth';
8
8
  export * from './event-logs';
9
+ export * from './settings';
10
+ export * from './telegraf';
@@ -0,0 +1,3 @@
1
+ export class EventSettingsDto {
2
+ [key: string]: boolean;
3
+ }
@@ -0,0 +1,19 @@
1
+ import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm';
2
+
3
+ @Entity('settings')
4
+ export class Setting {
5
+ @PrimaryGeneratedColumn('uuid')
6
+ id: string;
7
+
8
+ @Column({ unique: true })
9
+ key: string;
10
+
11
+ @Column()
12
+ value: string;
13
+
14
+ @CreateDateColumn({ name: 'created_at' })
15
+ createdAt: Date;
16
+
17
+ @UpdateDateColumn({ name: 'updated_at' })
18
+ updatedAt: Date;
19
+ }
@@ -0,0 +1,5 @@
1
+ export * from './settings.module';
2
+ export * from './settings.service';
3
+ export * from './settings.constants';
4
+ export * from './entities/setting.entity';
5
+ export * from './settings.permissions';
@@ -0,0 +1,8 @@
1
+ import { Setting } from "../entities/setting.entity";
2
+ import { Action, AppAbility, IPolicyHandler } from "../../casl";
3
+
4
+ export class ManageSettingsPolicy implements IPolicyHandler {
5
+ handle(ability: AppAbility) {
6
+ return ability.can(Action.Manage, Setting);
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ export enum Notifications {
2
+ QuotesByProviderStatus = 'notifications.quotes_by_provider_status',
3
+ QuotesByUserStatus = 'notifications.quotes_by_user_status',
4
+ }
5
+
6
+ export enum SettingsValues {
7
+ NO = 'no',
8
+ YES = 'yes',
9
+ }
@@ -0,0 +1,32 @@
1
+ import { Body, Controller, Get, Patch } from "@nestjs/common";
2
+ import { SettingsService } from "./settings.service";
3
+ import { ManageSettingsPolicy } from "./policies/manage-settings.policy";
4
+ import { ApiOkResponse, ApiTags } from "@nestjs/swagger";
5
+ import { CheckPolicies, ManageAllPolicy } from "../casl";
6
+ import { EventSettingsDto } from "./dto/event-settings.dto";
7
+
8
+ @CheckPolicies(new ManageAllPolicy())
9
+ @ApiTags('Settings')
10
+ @Controller('settings')
11
+ export class SettingsController {
12
+ constructor(
13
+ private readonly settingsService: SettingsService,
14
+ ) {}
15
+
16
+ @CheckPolicies(new ManageSettingsPolicy())
17
+ @Get('events')
18
+ @ApiOkResponse({
19
+ type: EventSettingsDto,
20
+ })
21
+ getEventSettings(): Promise<EventSettingsDto> {
22
+ return this.settingsService.getEventSettings();
23
+ }
24
+
25
+ @CheckPolicies(new ManageSettingsPolicy())
26
+ @Patch('events')
27
+ setEventSettings(
28
+ @Body() events: EventSettingsDto,
29
+ ) {
30
+ return this.settingsService.setEventSettings(events);
31
+ }
32
+ }
@@ -0,0 +1,46 @@
1
+ import { DynamicModule, Module } from '@nestjs/common';
2
+ import { getDataSourceToken, TypeOrmModule } from '@nestjs/typeorm';
3
+ import { SettingsController } from "./settings.controller";
4
+ import { SettingsService } from "./settings.service";
5
+ import { Setting } from "./entities/setting.entity";
6
+ import { DataSource } from "typeorm";
7
+ import { Action, CaslAbilityFactory } from "../casl";
8
+ import { SettingsPermissions } from "./settings.permissions";
9
+
10
+ @Module({})
11
+ export class SettingsModule {
12
+ static register(config: {
13
+ withControllers: boolean,
14
+ dataSourceName: string,
15
+ }): DynamicModule {
16
+ return {
17
+ module: SettingsModule,
18
+ imports: [
19
+ TypeOrmModule.forFeature([Setting], config.dataSourceName),
20
+ ],
21
+ controllers: config.withControllers ? [
22
+ SettingsController,
23
+ ] : [],
24
+ providers: [
25
+ {
26
+ provide: SettingsService,
27
+ inject: [getDataSourceToken(config.dataSourceName)],
28
+ useFactory: (dataSource: DataSource) => {
29
+ return new SettingsService(dataSource.getRepository(Setting));
30
+ }
31
+ }
32
+ ],
33
+ exports: [
34
+ SettingsService,
35
+ ],
36
+ };
37
+ }
38
+
39
+ constructor() {
40
+ CaslAbilityFactory.addPermissionToAction({
41
+ permission: SettingsPermissions.MANAGE,
42
+ action: Action.Manage,
43
+ subject: Setting,
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,3 @@
1
+ export enum SettingsPermissions {
2
+ MANAGE = 'manage_settings',
3
+ }
@@ -0,0 +1,51 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { In, Like, Repository } from "typeorm";
3
+ import { Setting } from "./entities/setting.entity";
4
+ import { EventSettingsDto } from "./dto/event-settings.dto";
5
+ import { SettingsValues } from "./settings.constants";
6
+
7
+ @Injectable()
8
+ export class SettingsService {
9
+ constructor(readonly repo: Repository<Setting>) {
10
+ }
11
+
12
+ public getSettings(settingsNames: string[]): Promise<Setting[]> {
13
+ return this.repo.find({
14
+ where: {
15
+ key: In(settingsNames),
16
+ }
17
+ });
18
+ }
19
+
20
+ async updateSettings(settings: Pick<Setting, 'key' | 'value'>[]): Promise<void> {
21
+ await this.repo.upsert(settings, ['key']);
22
+ }
23
+
24
+ async getEventSettings(): Promise<EventSettingsDto> {
25
+ const settings = await this.repo.find({
26
+ where: {
27
+ key: Like('notifications.%'),
28
+ }
29
+ });
30
+
31
+ const eventSettings = new EventSettingsDto();
32
+ settings.forEach(setting => {
33
+ eventSettings[setting.key] = setting.value === SettingsValues.YES
34
+ });
35
+
36
+ return eventSettings;
37
+ }
38
+
39
+ async setEventSettings(updateDto: EventSettingsDto): Promise<void> {
40
+ const settings: Pick<Setting, 'key' | 'value'>[] = [];
41
+
42
+ for (const key in updateDto) {
43
+ settings.push({
44
+ key: key,
45
+ value: updateDto[key] ? SettingsValues.YES : SettingsValues.NO,
46
+ });
47
+ }
48
+
49
+ await this.repo.upsert(settings, ['key']);
50
+ }
51
+ }
@@ -0,0 +1,13 @@
1
+ import Joi from "joi";
2
+ import { JoiSchema } from 'nestjs-joi';
3
+
4
+ export class TelegramSettingsUpdateDto {
5
+ @JoiSchema(Joi.boolean().optional())
6
+ enabled?: boolean;
7
+
8
+ @JoiSchema(Joi.string().trim().optional().allow(''))
9
+ botToken?: string;
10
+
11
+ @JoiSchema(Joi.string().trim().optional().allow(''))
12
+ chatId?: string;
13
+ }
@@ -0,0 +1,5 @@
1
+ export class TelegramSettingsDto {
2
+ enabled: boolean;
3
+ botToken: string | null;
4
+ chatId: string | null;
5
+ }
@@ -0,0 +1,3 @@
1
+ export * from './telegraf.module';
2
+ export * from './telegraf.service';
3
+ export * from './telegraf.constants';
@@ -0,0 +1,5 @@
1
+ export const Telegram = {
2
+ BotToken: 'telegram.bot_token',
3
+ ChatId: 'telegram.chat_id',
4
+ Enabled: 'telegram.enabled',
5
+ } as const;
@@ -0,0 +1,40 @@
1
+ import { Body, Controller, Get, Patch, Post } from "@nestjs/common";
2
+ import { TelegrafService } from "./telegraf.service";
3
+ import { ApiOkResponse, ApiTags } from "@nestjs/swagger";
4
+ import { ManageSettingsPolicy } from "../settings/policies/manage-settings.policy";
5
+ import { CheckPolicies, ManageAllPolicy } from "../casl";
6
+ import { TelegramSettingsDto } from "./dto/telegram-settings.dto";
7
+ import { TelegramSettingsUpdateDto } from "./dto/telegram-settings-update.dto";
8
+
9
+ @CheckPolicies(new ManageAllPolicy())
10
+ @ApiTags('Telegraf')
11
+ @Controller('telegraf')
12
+ export class TelegrafController {
13
+ constructor(
14
+ private readonly telegrafService: TelegrafService,
15
+ ) {
16
+ }
17
+
18
+ @CheckPolicies(new ManageSettingsPolicy())
19
+ @Get('telegram')
20
+ @ApiOkResponse({
21
+ type: TelegramSettingsDto,
22
+ })
23
+ getTelegramSettings(): Promise<TelegramSettingsDto> {
24
+ return this.telegrafService.getTelegramSettings();
25
+ }
26
+
27
+ @CheckPolicies(new ManageSettingsPolicy())
28
+ @Patch('telegram')
29
+ setTelegramSettings(
30
+ @Body() telegram: TelegramSettingsUpdateDto,
31
+ ) {
32
+ return this.telegrafService.setTelegramSettings(telegram);
33
+ }
34
+
35
+ @CheckPolicies(new ManageSettingsPolicy())
36
+ @Post('test')
37
+ async testTelegraf() {
38
+ return await this.telegrafService.test();
39
+ }
40
+ }
@@ -0,0 +1,28 @@
1
+ import { DynamicModule, Module } from "@nestjs/common";
2
+ import { TelegrafService } from "./telegraf.service";
3
+ import { SettingsModule } from "../settings/settings.module";
4
+ import { TelegrafController } from "./telegraf.controller";
5
+
6
+ @Module({})
7
+ export class TelegrafModule {
8
+ static register(config: {
9
+ withControllers: boolean,
10
+ dataSourceName: string,
11
+ }): DynamicModule {
12
+ return {
13
+ module: TelegrafModule,
14
+ imports: [
15
+ SettingsModule.register({ withControllers: false, dataSourceName: config.dataSourceName }),
16
+ ],
17
+ controllers: config.withControllers ? [
18
+ TelegrafController,
19
+ ] : [],
20
+ providers: [
21
+ TelegrafService,
22
+ ],
23
+ exports: [
24
+ TelegrafService,
25
+ ],
26
+ };
27
+ }
28
+ }
@@ -0,0 +1,110 @@
1
+ import { BadRequestException, Injectable, Logger } from "@nestjs/common";
2
+ import { Notifications, Setting, SettingsService, SettingsValues } from "../settings";
3
+ import { FmtString } from "telegraf/format";
4
+
5
+ import { Telegraf } from "telegraf";
6
+ import { Telegram } from "./telegraf.constants";
7
+ import { TelegramSettingsDto } from "./dto/telegram-settings.dto";
8
+ import { TelegramSettingsUpdateDto } from "./dto/telegram-settings-update.dto";
9
+
10
+ export const settingsToDtoPropMap = {
11
+ [Telegram.Enabled]: 'enabled',
12
+ [Telegram.BotToken]: 'botToken',
13
+ [Telegram.ChatId]: 'chatId',
14
+ } as const;
15
+
16
+ @Injectable()
17
+ export class TelegrafService {
18
+ private readonly logger = new Logger(TelegrafService.name);
19
+
20
+ constructor(
21
+ private readonly settingsService: SettingsService,
22
+ ) {
23
+ }
24
+
25
+ async getTelegramSettings(): Promise<TelegramSettingsDto> {
26
+ const settings = await this.settingsService.getSettings([
27
+ Telegram.Enabled,
28
+ Telegram.BotToken,
29
+ Telegram.ChatId
30
+ ])
31
+
32
+ const telegramSettings = {
33
+ enabled: false,
34
+ botToken: '',
35
+ chatId: '',
36
+ };
37
+ settings.forEach((setting) => {
38
+ switch (setting.key) {
39
+ case Telegram.Enabled:
40
+ telegramSettings[settingsToDtoPropMap[setting.key]] = setting.value === SettingsValues.YES;
41
+ break;
42
+
43
+ case Telegram.BotToken:
44
+ case Telegram.ChatId:
45
+ telegramSettings[settingsToDtoPropMap[setting.key]] = setting.value;
46
+ break;
47
+ }
48
+ });
49
+
50
+ return telegramSettings;
51
+ }
52
+
53
+ async setTelegramSettings(updateDto: TelegramSettingsUpdateDto): Promise<void> {
54
+ const settings: Pick<Setting, 'key' | 'value'>[] = [];
55
+
56
+ if (updateDto.enabled) {
57
+ settings.push({
58
+ key: Telegram.Enabled,
59
+ value: updateDto.enabled ? SettingsValues.YES : SettingsValues.NO,
60
+ });
61
+ }
62
+
63
+ if (updateDto.botToken) {
64
+ settings.push({
65
+ key: Telegram.BotToken,
66
+ value: updateDto.botToken,
67
+ });
68
+ }
69
+
70
+ if (updateDto.chatId) {
71
+ settings.push({
72
+ key: Telegram.ChatId,
73
+ value: updateDto.chatId,
74
+ });
75
+ }
76
+
77
+ await this.settingsService.updateSettings(settings);
78
+ }
79
+
80
+ async sendMessage(message: string | FmtString, type: Notifications | null): Promise<void> {
81
+ const config = await this.getTelegramSettings();
82
+ if (!config.enabled) {
83
+ this.logger.log('Suppressed telegram message because it is disabled');
84
+ return;
85
+ }
86
+
87
+ if (!config.botToken || !config.chatId) {
88
+ this.logger.log('Suppressed telegram message because bot token or chat id is not set');
89
+ return;
90
+ }
91
+
92
+ const bot = new Telegraf(config.botToken);
93
+
94
+ try {
95
+ await bot.telegram.sendMessage(config.chatId, message);
96
+ } catch (e) {
97
+ this.logger.error('Failed to send telegram message');
98
+ this.logger.error(e, e.stack);
99
+ }
100
+ }
101
+
102
+ async test() {
103
+ try {
104
+ return await this.sendMessage('Test message', null);
105
+ } catch (e) {
106
+ this.logger.error(e, e.stack);
107
+ throw new BadRequestException('Wrong bot token or chat id')
108
+ }
109
+ }
110
+ }