Repository: js-tasks-ru/nodejs-20250303_amozalev
Pull request #3: Notification service
Author: login: amozalev
Created: 2025-03-21T18:02:25Z | Merged: 2025-03-25T18:00:40Z



Diff:
diff --git a/.gitignore b/.gitignore
index 931992d..f574aa8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@ package-lock.json
 yarn.lock
 dist
 .sqlite
-errors.log
\ No newline at end of file
+errors.log
+
+.env.*
\ No newline at end of file
diff --git a/03-di/01-notification-service/app.module.ts b/03-di/01-notification-service/app.module.ts
index 3d6655f..30ce239 100644
--- a/03-di/01-notification-service/app.module.ts
+++ b/03-di/01-notification-service/app.module.ts
@@ -1,7 +1,13 @@
 import { Module } from "@nestjs/common";
 import { TasksModule } from "./tasks/tasks.module";
+import { NotificationsModule } from "./notifications/notifications.module";
+import { UsersModule } from "./users/users.module";
 
 @Module({
-  imports: [TasksModule],
+  imports: [
+    TasksModule,
+    NotificationsModule,
+    UsersModule,
+  ],
 })
 export class AppModule {}
diff --git a/03-di/01-notification-service/notifications/notifications.module.ts b/03-di/01-notification-service/notifications/notifications.module.ts
index 3fd6cfe..5d50e03 100644
--- a/03-di/01-notification-service/notifications/notifications.module.ts
+++ b/03-di/01-notification-service/notifications/notifications.module.ts
@@ -1,8 +1,17 @@
 import { Module } from "@nestjs/common";
 import { NotificationsService } from "./notifications.service";
+import { Configuration } from "./types";
+import { ValueProvider } from "@nestjs/common/interfaces/modules/provider.interface";
+
+const configurationProvider: ValueProvider<Configuration> = {
+  provide: "CONFIG",
+  useValue: {
+    senderEmail: "task_service_notificator@mail.com",
+  },
+};
 
 @Module({
-  providers: [NotificationsService],
+  providers: [NotificationsService, configurationProvider],
   exports: [NotificationsService],
 })
 export class NotificationsModule {}
diff --git a/03-di/01-notification-service/notifications/notifications.service.ts b/03-di/01-notification-service/notifications/notifications.service.ts
index 96a5cea..d22229c 100644
--- a/03-di/01-notification-service/notifications/notifications.service.ts
+++ b/03-di/01-notification-service/notifications/notifications.service.ts
@@ -1,4 +1,42 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Inject, Injectable } from "@nestjs/common";
+import { Configuration } from "./types";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor(@Inject("CONFIG") config: Configuration) {
+    console.log("Config:", config.senderEmail);
+  }
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Email пустой");
+    }
+
+    const logMsg = `Email sent to ${to}: [${subject}] ${message}`;
+    this.log(logMsg);
+  }
+
+  sendSMS(to: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Номер телефона пустой");
+    }
+
+    const logMsg = `SMS sent to ${to}: ${message}`;
+    this.log(logMsg);
+  }
+
+  log(logMsg: string) {
+    const logMsgWithDate = `${new Date().toDateString()}: ${logMsg}\n`;
+
+    fs.appendFile(this.logFile, logMsgWithDate, (err) => {
+      if (err) {
+        console.error(err);
+      }
+    });
+    console.log(logMsg);
+  }
+}
diff --git a/03-di/01-notification-service/notifications/types.ts b/03-di/01-notification-service/notifications/types.ts
new file mode 100644
index 0000000..42e1f18
--- /dev/null
+++ b/03-di/01-notification-service/notifications/types.ts
@@ -0,0 +1,3 @@
+export type Configuration = {
+  senderEmail: string;
+}
\ No newline at end of file
diff --git a/03-di/01-notification-service/tasks/tasks.module.ts b/03-di/01-notification-service/tasks/tasks.module.ts
index adecf42..db16325 100644
--- a/03-di/01-notification-service/tasks/tasks.module.ts
+++ b/03-di/01-notification-service/tasks/tasks.module.ts
@@ -1,9 +1,11 @@
 import { Module } from "@nestjs/common";
 import { TasksController } from "./tasks.controller";
 import { TasksService } from "./tasks.service";
+import { NotificationsModule } from "../notifications/notifications.module";
+import { UsersModule } from "../users/users.module";
 
 @Module({
-  imports: [],
+  imports: [NotificationsModule, UsersModule],
   controllers: [TasksController],
   providers: [TasksService],
 })
diff --git a/03-di/01-notification-service/tasks/tasks.service.ts b/03-di/01-notification-service/tasks/tasks.service.ts
index 5c81948..e68d237 100644
--- a/03-di/01-notification-service/tasks/tasks.service.ts
+++ b/03-di/01-notification-service/tasks/tasks.service.ts
@@ -1,14 +1,25 @@
 import { Injectable, NotFoundException } from "@nestjs/common";
 import { CreateTaskDto, Task, TaskStatus, UpdateTaskDto } from "./task.model";
+import { NotificationsService } from "../notifications/notifications.service";
+import { UsersService } from "../users/users.service";
 
 @Injectable()
 export class TasksService {
   private tasks: Task[] = [];
 
-  constructor() {}
+  constructor(
+    private notificationsService: NotificationsService,
+    private usersService: UsersService,
+  ) {}
 
   async createTask(createTaskDto: CreateTaskDto) {
     const { title, description, assignedTo } = createTaskDto;
+
+    const user = this.usersService.getUserById(assignedTo);
+    if (!user) {
+      throw new NotFoundException(`Пользователь не найден`);
+    }
+
     const task: Task = {
       id: (this.tasks.length + 1).toString(),
       title,
@@ -18,6 +29,12 @@ export class TasksService {
     };
     this.tasks.push(task);
 
+    this.notificationsService.sendEmail(
+      user.email,
+      "Новая задача",
+      `Вы назначены ответственным за задачу: "${title}"`,
+    );
+
     return task;
   }
 
@@ -27,6 +44,16 @@ export class TasksService {
       throw new NotFoundException(`Задача с ID ${id} не найдена`);
     }
 
+    const user = this.usersService.getUserById(task.assignedTo);
+    if (!user) {
+      throw new NotFoundException(`Пользователь не найден`);
+    }
+
+    this.notificationsService.sendSMS(
+      user.phone,
+      `Статус задачи "${updateTaskDto.title ?? task.title}" обновлён на "${updateTaskDto.status}"`,
+    );
+
     Object.assign(task, updateTaskDto);
     return task;
   }


Reviews:
user: login: tadjik1
body: В целом задача выполнена хорошо, но есть несколько моментов, которые нужно поправить для корректной работы и улучшения качества кода.

> Т.е. главный вопрос, надо было реализовывать не используя "@nestjs/config"

необязательно, на самом деле. достаточно просто передавать значения в конструкторе (даже если они статические сейчас). в будущем мы обсудим как брать их из конфига, например
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-25T16:34:03Z
user: login: amozalev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:37:21Z
user: login: amozalev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:37:42Z
user: login: amozalev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:38:00Z
user: login: amozalev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:38:21Z
user: login: amozalev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:38:35Z
user: login: amozalev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:40:29Z
user: login: tadjik1
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-25T17:59:59Z
user: login: tadjik1
body: спасибо за исправления, отличная работа 👍 
state: APPROVED
author_association: NONE
submitted_at: 2025-03-25T18:00:15Z

Review comments:
diff_hunk: @@ -1,7 +1,15 @@
 import { Module } from "@nestjs/common";
 import { TasksModule } from "./tasks/tasks.module";
+import { NotificationsModule } from "./notifications/notifications.module";
+import { NotificationsService } from "./notifications/notifications.service";
+import { UsersModule } from "./users/users.module";
 
 @Module({
-  imports: [TasksModule],
+  imports: [
+    TasksModule,
+    NotificationsModule,
+    UsersModule,
+  ],
+  providers: [NotificationsService],
path: 03-di/01-notification-service/app.module.ts
user: login: tadjik1
body: Мне кажется, здесь не нужно явно указывать NotificationsService в providers, так как он уже предоставляется через NotificationsModule.
created_at: 2025-03-25T16:31:54Z
author_association: NONE
diff_hunk: @@ -1,4 +1,39 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Injectable } from "@nestjs/common";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor() {}
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `Email sent to ${to}: [Новая задача] Вы назначены ответственным за задачу: "${subject}"`;
path: 03-di/01-notification-service/notifications/notifications.service.ts
user: login: tadjik1
body: Лучше использовать переданный параметр subject и message для формирования текста уведомления, а не фиксированную строку.
created_at: 2025-03-25T16:31:54Z
author_association: NONE
diff_hunk: @@ -1,4 +1,39 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Injectable } from "@nestjs/common";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor() {}
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `Email sent to ${to}: [Новая задача] Вы назначены ответственным за задачу: "${subject}"`;
+    this.log(logMsg);
+  }
+
+  sendSMS(to: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
path: 03-di/01-notification-service/notifications/notifications.service.ts
user: login: tadjik1
body: Здесь ошибка в сообщении исключения: указано 'Поле Email пустое', хотя проверяется номер телефона.
created_at: 2025-03-25T16:31:54Z
author_association: NONE
diff_hunk: @@ -1,4 +1,39 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Injectable } from "@nestjs/common";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor() {}
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `Email sent to ${to}: [Новая задача] Вы назначены ответственным за задачу: "${subject}"`;
+    this.log(logMsg);
+  }
+
+  sendSMS(to: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `SMS sent to ${to}: Статус задачи "Сделать домашнюю работу" изменён на "completed"`;
path: 03-di/01-notification-service/notifications/notifications.service.ts
user: login: tadjik1
body: Рекомендую использовать переданный параметр message вместо фиксированной строки, чтобы метод был универсальным.
created_at: 2025-03-25T16:31:54Z
author_association: NONE
diff_hunk: @@ -27,6 +44,16 @@ export class TasksService {
       throw new NotFoundException(`Задача с ID ${id} не найдена`);
     }
 
+    const user = this.usersService.getUserById(task.assignedTo);
+    if (!user) {
+      throw new NotFoundException(`Пользователь не найден`);
+    }
+
+    this.notificationsService.sendSMS(
+      user.phone,
+      `Статус задачи "${updateTaskDto.title ?? task.title}" обновлён на "completed"`,
path: 03-di/01-notification-service/tasks/tasks.service.ts
user: login: tadjik1
body: Здесь статус задачи всегда указан как 'completed', хотя он может быть разным. Лучше использовать актуальное значение из updateTaskDto.status.
created_at: 2025-03-25T16:31:54Z
author_association: NONE
diff_hunk: @@ -1,7 +1,15 @@
 import { Module } from "@nestjs/common";
 import { TasksModule } from "./tasks/tasks.module";
+import { NotificationsModule } from "./notifications/notifications.module";
+import { NotificationsService } from "./notifications/notifications.service";
+import { UsersModule } from "./users/users.module";
 
 @Module({
-  imports: [TasksModule],
+  imports: [
+    TasksModule,
+    NotificationsModule,
+    UsersModule,
+  ],
+  providers: [NotificationsService],
path: 03-di/01-notification-service/app.module.ts
user: login: amozalev
body: Fixed
created_at: 2025-03-25T17:37:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,4 +1,39 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Injectable } from "@nestjs/common";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor() {}
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `Email sent to ${to}: [Новая задача] Вы назначены ответственным за задачу: "${subject}"`;
path: 03-di/01-notification-service/notifications/notifications.service.ts
user: login: amozalev
body: Fixed
created_at: 2025-03-25T17:37:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,4 +1,39 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Injectable } from "@nestjs/common";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor() {}
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `Email sent to ${to}: [Новая задача] Вы назначены ответственным за задачу: "${subject}"`;
+    this.log(logMsg);
+  }
+
+  sendSMS(to: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
path: 03-di/01-notification-service/notifications/notifications.service.ts
user: login: amozalev
body: Fixed
created_at: 2025-03-25T17:38:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,4 +1,39 @@
-import { Injectable } from "@nestjs/common";
+import * as fs from "node:fs";
+import * as path from "node:path";
+import { BadRequestException, Injectable } from "@nestjs/common";
 
 @Injectable()
-export class NotificationsService {}
+export class NotificationsService {
+  private readonly logFile = path.resolve(__dirname, "file.log");
+
+  constructor() {}
+
+  sendEmail(to: string, subject: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `Email sent to ${to}: [Новая задача] Вы назначены ответственным за задачу: "${subject}"`;
+    this.log(logMsg);
+  }
+
+  sendSMS(to: string, message: string): void {
+    if (!to) {
+      throw new BadRequestException("Поле Email пустое");
+    }
+
+    const logMsg = `SMS sent to ${to}: Статус задачи "Сделать домашнюю работу" изменён на "completed"`;
path: 03-di/01-notification-service/notifications/notifications.service.ts
user: login: amozalev
body: Fixed
created_at: 2025-03-25T17:38:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,6 +44,16 @@ export class TasksService {
       throw new NotFoundException(`Задача с ID ${id} не найдена`);
     }
 
+    const user = this.usersService.getUserById(task.assignedTo);
+    if (!user) {
+      throw new NotFoundException(`Пользователь не найден`);
+    }
+
+    this.notificationsService.sendSMS(
+      user.phone,
+      `Статус задачи "${updateTaskDto.title ?? task.title}" обновлён на "completed"`,
path: 03-di/01-notification-service/tasks/tasks.service.ts
user: login: amozalev
body: Fixed
created_at: 2025-03-25T17:38:35Z
author_association: CONTRIBUTOR
path: 03-di/01-notification-service/notifications/types.ts
user: login: amozalev
body: @tadjik1 куда лучше положить тип конфигурации? так нормально?
created_at: 2025-03-25T17:40:29Z
author_association: CONTRIBUTOR
path: 03-di/01-notification-service/notifications/types.ts
user: login: tadjik1
body: да, вполне 👍 
created_at: 2025-03-25T17:59:59Z
author_association: NONE

PR comments:
user: login: jsru-1
created_at: 2025-03-21T18:03:42Z
body: Проверьте, пожалуйста, ваше решение, не все тесты прошли (PR не будет принят до тех пор, пока **все** добавленные задачи не будут решены).


author_association: CONTRIBUTOR
user: login: jsru-1
created_at: 2025-03-21T18:16:20Z
body: Добавляю преподавателя (@tadjik1) для код-ревью.
author_association: CONTRIBUTOR
user: login: tadjik1
created_at: 2025-03-22T15:43:16Z
body: @amozalev ПР в драфте, он еще не готов или уже можно ревьюить?
author_association: NONE
user: login: jsru-1
created_at: 2025-03-22T16:23:06Z
body: Решение было обновлено, посмотрим что скажет @tadjik1
author_association: CONTRIBUTOR
user: login: amozalev
created_at: 2025-03-22T16:31:19Z
body: > @amozalev ПР в драфте, он еще не готов или уже можно ревьюить?

@tadjik1 , я не понял задачу "Добавьте возможность передавать конфигурацию (например, senderEmail или smsGateway) через параметры конструктора." В коммите [bcf5977f3071d70c76667b9a711c3bae38844940](https://github.com/js-tasks-ru/nodejs-20250303_amozalev/pull/3/commits/bcf5977f3071d70c76667b9a711c3bae38844940) я реализовал её с у становкой пакета "@nestjs/config". В пайплайне упали тесты и я добавил пакет в корневой package.json и тесты в пайплайне прошли https://github.com/js-tasks-ru/nodejs-20250303_amozalev/pull/3/commits/53297533dbb3f86fa59024ff2474dc5e63dac489
После этого поломались типы в файле тестов. Понятное дело, спасло as any. Тут стало понятно, что что-то не то делаю. В итоге удалил эту реализацию. Т.е. главный вопрос, надо было реализовывать не используя "@nestjs/config"?

Да, проверь, пожалуйста, PR

author_association: CONTRIBUTOR
user: login: jsru-1
created_at: 2025-03-25T17:37:39Z
body: Добавляю преподавателя (@tadjik1) для код-ревью.
author_association: CONTRIBUTOR