Repository: AnnemariaRe/finance-app
Pull request #20: Remove cyclic class dependencies to create mocks
Author: login: Onexx
Created: 2025-05-20T08:06:41Z | Merged: 2025-05-21T14:09:11Z

- Remove all many-to-one relations as they create cyclic dependencies that are impossible to mock
- Add favicon.ico to avoid errors in logs
- Extract sensitive info into .env file to be able to change it in production
- Recreate migrations as changes were mixed and were not working


Diff:
diff --git a/.env b/.env
new file mode 100644
index 0000000..0f82a89
--- /dev/null
+++ b/.env
@@ -0,0 +1,4 @@
+DB_URL=postgres://user:pg_password@db/postgres_db
+BOT_TOKEN=8043688697:AAHQUtSLrE7xZWNJIQB4RvytMBzA9qHLJFc
+CHAT_ID=-4798165321
+PORT=3000
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 83ee189..04cc0d4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -13,6 +13,9 @@ services:
       - /app/node_modules
     environment:
       - NODE_ENV=development
+      - DB_URL=postgres://user:pg_password@db/postgres_db
+      - BOT_TOKEN=8043688697:AAHQUtSLrE7xZWNJIQB4RvytMBzA9qHLJFc
+      - CHAT_ID=-4798165321
     restart: unless-stopped
     networks:
       - app_network
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..b7a2cf0
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/account/accounts.service.ts b/src/account/accounts.service.ts
index 2d19af9..3ab1246 100644
--- a/src/account/accounts.service.ts
+++ b/src/account/accounts.service.ts
@@ -58,24 +58,22 @@ export default class AccountsService {
   }
 
   async findAll() {
-    const account = await this.accountRepository.find();
-    return account;
+    return await this.accountRepository.find();
   }
 
   async findAllActiveByUserId(userId: number) {
-    const user = await this.userRepository.findOne({
-      where: { id: userId },
-      relations: ['accounts'],
+    return await this.accountRepository.find({
+      relations: ['user'],
+      where: { user: { id: userId }, isActive: true },
     });
-    return user.accounts;
   }
 
   async findAllByUserId(userId: number) {
-    const user = await this.userRepository.findOne({
-      where: { id: userId },
-      relations: ['accounts.currency'],
+    return await this.accountRepository.find({
+      relations: ['user'],
+      where: { user: { id: userId } },
+      order: { createdAt: 1 },
     });
-    if (user && user.accounts != null) return user.accounts;
   }
 
   async delete(id: number) {
diff --git a/src/app.module.ts b/src/app.module.ts
index 29c8163..c4a75c9 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -1,24 +1,17 @@
 import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
 import { TypeOrmModule } from '@nestjs/typeorm';
 import { APP_INTERCEPTOR } from '@nestjs/core';
-import { DemoController } from './demo/demo.controller';
-import { DemoService } from './demo/demo.service';
-import { User } from './entities/user.entity';
 import { UsersModule } from './users/users.module';
 import { ResponseTimeInterceptor } from './response-time.interceptor';
 import { RequestLoggerMiddleware } from './logger/request-logger.middleware';
 import { AppLoggerService } from './logger/app-logger.service';
-import { Account } from './entities/account.entity';
-import { Currency } from './entities/currency.entity';
-import { Category } from './entities/category.entity';
-import { Transaction } from './entities/transaction.entity';
 import { WalletModule } from './wallet/wallet.module';
 import { HistoryModule } from './history/history.module';
+import { DemoModule } from './demo/demo.module';
+import { ConfigModule } from '@nestjs/config';
 
 @Module({
-  controllers: [DemoController],
   providers: [
-    DemoService,
     AppLoggerService,
     {
       provide: APP_INTERCEPTOR,
@@ -26,13 +19,15 @@ import { HistoryModule } from './history/history.module';
     },
   ],
   imports: [
+    ConfigModule.forRoot({ isGlobal: true }),
+    DemoModule,
     UsersModule,
     WalletModule,
     HistoryModule,
     TypeOrmModule.forRoot({
       type: 'postgres',
-      url: 'postgres://user:pg_password@db/postgres_db',
-      entities: [User, Account, Currency, Category, Transaction],
+      url: process.env.DB_URL,
+      autoLoadEntities: true,
     }),
   ],
 })
diff --git a/src/categories/catigories.service.ts b/src/categories/catigories.service.ts
index 6b9901e..3b20e61 100644
--- a/src/categories/catigories.service.ts
+++ b/src/categories/catigories.service.ts
@@ -1,20 +1,19 @@
 import { Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
-import { User } from 'src/entities/user.entity';
 import { Repository } from 'typeorm';
+import { Category } from '../entities/category.entity';
 
 @Injectable()
 export default class CategoriesService {
   constructor(
-    @InjectRepository(User)
-    private readonly userRepository: Repository<User>,
+    @InjectRepository(Category)
+    private readonly categoryRepository: Repository<Category>,
   ) {}
 
   async findCategoriesByUserId(userId: number) {
-    const user = await this.userRepository.findOne({
-      where: { id: userId },
-      relations: ['categories'],
+    return this.categoryRepository.find({
+      relations: ['user'],
+      where: { user: { id: userId } },
     });
-    return user.categories;
   }
 }
diff --git a/src/db/migrations/1747726488259-migration.ts b/src/db/migrations/1747726488259-migration.ts
new file mode 100644
index 0000000..4dba202
--- /dev/null
+++ b/src/db/migrations/1747726488259-migration.ts
@@ -0,0 +1,69 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class Migration1747726488259 implements MigrationInterface {
+  name = 'Migration1747726488259';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(
+      `CREATE TABLE "user" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "email" character varying NOT NULL, "name" character varying NOT NULL, "password" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`,
+    );
+    await queryRunner.query(
+      `CREATE TABLE "currency" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "name" character varying NOT NULL, "code" character varying(3), "symbol" character NOT NULL, CONSTRAINT "PK_3cda65c731a6264f0e444cc9b91" PRIMARY KEY ("id"))`,
+    );
+    await queryRunner.query(
+      `CREATE TYPE "public"."account_accounttype_enum" AS ENUM('Основной', 'Сберегательный', 'Кредитный', 'Наличные')`,
+    );
+    await queryRunner.query(
+      `CREATE TABLE "account" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "title" character varying NOT NULL, "isActive" boolean NOT NULL DEFAULT true, "accountType" "public"."account_accounttype_enum" NOT NULL, "userId" integer, "currencyId" integer, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`,
+    );
+    await queryRunner.query(
+      `CREATE TYPE "public"."category_operationtype_enum" AS ENUM('expense', 'income')`,
+    );
+    await queryRunner.query(
+      `CREATE TABLE "category" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "name" character varying NOT NULL, "operationType" "public"."category_operationtype_enum" NOT NULL, "userId" integer, CONSTRAINT "PK_9c4e4a89e3674fc9f382d733f03" PRIMARY KEY ("id"))`,
+    );
+    await queryRunner.query(
+      `CREATE TABLE "transaction" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "amount" numeric(10,2) NOT NULL DEFAULT '0', "date" date NOT NULL, "accountId" integer, "categoryId" integer, CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9" PRIMARY KEY ("id"))`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "account" ADD CONSTRAINT "FK_60328bf27019ff5498c4b977421" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "account" ADD CONSTRAINT "FK_f6cf13404290564d6992f5e4158" FOREIGN KEY ("currencyId") REFERENCES "currency"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "category" ADD CONSTRAINT "FK_32b856438dffdc269fa84434d9f" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "transaction" ADD CONSTRAINT "FK_3d6e89b14baa44a71870450d14d" FOREIGN KEY ("accountId") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "transaction" ADD CONSTRAINT "FK_d3951864751c5812e70d033978d" FOREIGN KEY ("categoryId") REFERENCES "category"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
+    );
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(
+      `ALTER TABLE "transaction" DROP CONSTRAINT "FK_d3951864751c5812e70d033978d"`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "transaction" DROP CONSTRAINT "FK_3d6e89b14baa44a71870450d14d"`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "category" DROP CONSTRAINT "FK_32b856438dffdc269fa84434d9f"`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "account" DROP CONSTRAINT "FK_f6cf13404290564d6992f5e4158"`,
+    );
+    await queryRunner.query(
+      `ALTER TABLE "account" DROP CONSTRAINT "FK_60328bf27019ff5498c4b977421"`,
+    );
+    await queryRunner.query(`DROP TABLE "transaction"`);
+    await queryRunner.query(`DROP TABLE "category"`);
+    await queryRunner.query(`DROP TYPE "public"."category_operationtype_enum"`);
+    await queryRunner.query(`DROP TABLE "account"`);
+    await queryRunner.query(`DROP TYPE "public"."account_accounttype_enum"`);
+    await queryRunner.query(`DROP TABLE "currency"`);
+    await queryRunner.query(`DROP TABLE "user"`);
+  }
+}
diff --git a/src/db/migrations/add-transactions-to-others.ts b/src/db/migrations/add-transactions-to-others.ts
deleted file mode 100644
index d97197d..0000000
--- a/src/db/migrations/add-transactions-to-others.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class Migration1747496425120 implements MigrationInterface {
-  name = 'Migration1747496425120';
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `ALTER TABLE "transaction" ADD "accountId" integer`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "transaction" ADD "categoryId" integer`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "transaction" ADD CONSTRAINT "FK_3d6e89b14baa44a71870450d14d" FOREIGN KEY ("accountId") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "transaction" ADD CONSTRAINT "FK_d3951864751c5812e70d033978d" FOREIGN KEY ("categoryId") REFERENCES "category"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
-    );
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `ALTER TABLE "transaction" DROP CONSTRAINT "FK_d3951864751c5812e70d033978d"`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "transaction" DROP CONSTRAINT "FK_3d6e89b14baa44a71870450d14d"`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "transaction" DROP COLUMN "categoryId"`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "transaction" DROP COLUMN "accountId"`,
-    );
-  }
-}
diff --git a/src/db/migrations/create-category.ts b/src/db/migrations/create-category.ts
deleted file mode 100644
index fb9b0cf..0000000
--- a/src/db/migrations/create-category.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class Migration1747493844289 implements MigrationInterface {
-  name = 'Migration1747493844289';
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `CREATE TYPE "public"."category_operationtype_enum" AS ENUM('expense', 'income')`,
-    );
-    await queryRunner.query(
-      `CREATE TABLE "category" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "name" character varying NOT NULL, "operationType" "public"."category_operationtype_enum" NOT NULL, "userId" integer, CONSTRAINT "PK_9c4e4a89e3674fc9f382d733f03" PRIMARY KEY ("id"))`,
-    );
-    await queryRunner.query(
-      `CREATE TYPE "public"."account_accounttype_enum" AS ENUM('Основной', 'Сберегательный', 'Кредитный', 'Наличные')`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "account" ALTER COLUMN "accountType" TYPE "public"."account_accounttype_enum" USING "accountType"::"text"::"public"."account_accounttype_enum"`,
-    );
-    await queryRunner.query(
-      `DROP TYPE "public"."account_accounttype_enum_old"`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "category" ADD CONSTRAINT "FK_32b856438dffdc269fa84434d9f" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
-    );
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `ALTER TABLE "category" DROP CONSTRAINT "FK_32b856438dffdc269fa84434d9f"`,
-    );
-    await queryRunner.query(
-      `CREATE TYPE "public"."account_accounttype_enum_old" AS ENUM('SAVINGS', 'CURRENT', 'CASH', 'CREDIT')`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "account" ALTER COLUMN "accountType" TYPE "public"."account_accounttype_enum_old" USING "accountType"::"text"::"public"."account_accounttype_enum_old"`,
-    );
-    await queryRunner.query(`DROP TYPE "public"."account_accounttype_enum"`);
-    await queryRunner.query(
-      `ALTER TYPE "public"."account_accounttype_enum_old" RENAME TO "account_accounttype_enum"`,
-    );
-    await queryRunner.query(`DROP TABLE "category"`);
-    await queryRunner.query(`DROP TYPE "public"."category_operationtype_enum"`);
-  }
-}
diff --git a/src/db/migrations/create-currency.ts b/src/db/migrations/create-currency.ts
deleted file mode 100644
index 3f8f7b2..0000000
--- a/src/db/migrations/create-currency.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class Migration1747472320381 implements MigrationInterface {
-  name = 'Migration1747472320381';
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `CREATE TABLE "currency" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "name" character varying NOT NULL, "code" character varying(3), "symbol" character NOT NULL, CONSTRAINT "PK_3cda65c731a6264f0e444cc9b91" PRIMARY KEY ("id"))`,
-    );
-    await queryRunner.query(
-      `CREATE TABLE "account" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "title" character varying NOT NULL, "isActive" boolean NOT NULL DEFAULT true, "accountType" "public"."account_accounttype_enum" NOT NULL, "userId" integer, "currencyId" integer, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "account" ADD CONSTRAINT "FK_60328bf27019ff5498c4b977421" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "account" ADD CONSTRAINT "FK_f6cf13404290564d6992f5e4158" FOREIGN KEY ("currencyId") REFERENCES "currency"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
-    );
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `ALTER TABLE "account" DROP CONSTRAINT "FK_f6cf13404290564d6992f5e4158"`,
-    );
-    await queryRunner.query(
-      `ALTER TABLE "account" DROP CONSTRAINT "FK_60328bf27019ff5498c4b977421"`,
-    );
-    await queryRunner.query(`DROP TABLE "account"`);
-    await queryRunner.query(`DROP TABLE "currency"`);
-  }
-}
diff --git a/src/db/migrations/create-transaction.ts b/src/db/migrations/create-transaction.ts
deleted file mode 100644
index 95c5624..0000000
--- a/src/db/migrations/create-transaction.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm';
-
-export class Migration1747495159358 implements MigrationInterface {
-  name = 'Migration1747495159358';
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      `CREATE TABLE "transaction" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "amount" numeric(10,2) NOT NULL DEFAULT '0', "date" date NOT NULL, CONSTRAINT "PK_89eadb93a89810556e1cbcd6ab9" PRIMARY KEY ("id"))`,
-    );
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(`DROP TABLE "transaction"`);
-  }
-}
diff --git a/src/db/migrations/currency-data.ts b/src/db/migrations/currency-data.ts
index d5aed86..1feb96c 100644
--- a/src/db/migrations/currency-data.ts
+++ b/src/db/migrations/currency-data.ts
@@ -1,6 +1,6 @@
 import { MigrationInterface, QueryRunner } from 'typeorm';
 
-export class CurrencyTable1747472320382 implements MigrationInterface {
+export class CurrencyTable9999999999998 implements MigrationInterface {
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(`
           INSERT INTO currency (name, code, symbol, "createdAt") VALUES
diff --git a/src/db/migrations/user-data.ts b/src/db/migrations/user-data.ts
index 61269b1..e2409c2 100644
--- a/src/db/migrations/user-data.ts
+++ b/src/db/migrations/user-data.ts
@@ -1,6 +1,6 @@
 import { MigrationInterface, QueryRunner } from 'typeorm';
 
-export class SeedUserTable1715960000000 implements MigrationInterface {
+export class SeedUserTable9999999999999 implements MigrationInterface {
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(`
         INSERT INTO "user" (email, name, password, "createdAt", "updatedAt") VALUES
diff --git a/src/entities/account.entity.ts b/src/entities/account.entity.ts
index 57b8ff1..b58e08c 100644
--- a/src/entities/account.entity.ts
+++ b/src/entities/account.entity.ts
@@ -1,13 +1,11 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { Currency } from './currency.entity';
-import { Transaction } from '../entities/transaction.entity';
 import { AccountType } from '../enums/AccountType';
-import { User } from '../entities/user.entity';
+import { User } from './user.entity';
 import {
   Column,
   CreateDateColumn,
   Entity,
-  OneToMany,
   ManyToOne,
   PrimaryGeneratedColumn,
   UpdateDateColumn,
@@ -43,16 +41,10 @@ export class Account {
   accountType: AccountType;
 
   @ApiProperty({ type: () => User })
-  @ManyToOne(() => User, (user) => user.accounts)
+  @ManyToOne(() => User)
   user: User;
 
   @ApiProperty({ type: () => Currency })
-  @ManyToOne(() => Currency, (currency) => currency.accounts)
+  @ManyToOne(() => Currency)
   currency: Currency;
-
-  @ApiProperty({ type: () => Transaction })
-  @OneToMany(() => Transaction, (transaction) => transaction.account, {
-    cascade: true,
-  })
-  transactions: Transaction[];
 }
diff --git a/src/entities/category.entity.ts b/src/entities/category.entity.ts
index dbec3b3..1cbdc58 100644
--- a/src/entities/category.entity.ts
+++ b/src/entities/category.entity.ts
@@ -1,13 +1,11 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { OperationType } from 'src/enums/OperationType';
-import { Transaction } from '../entities/transaction.entity';
 import { User } from 'src/entities/user.entity';
 import {
   Column,
   CreateDateColumn,
   Entity,
   ManyToOne,
-  OneToMany,
   PrimaryGeneratedColumn,
 } from 'typeorm';
 
@@ -33,12 +31,6 @@ export class Category {
   operationType: OperationType;
 
   @ApiProperty({ type: () => User })
-  @ManyToOne(() => User, (user) => user.accounts, { nullable: true })
+  @ManyToOne(() => User)
   user: User;
-
-  @ApiProperty({ type: () => Transaction })
-  @OneToMany(() => Transaction, (transaction) => transaction.category, {
-    cascade: true,
-  })
-  transactions: Transaction[];
 }
diff --git a/src/entities/currency.entity.ts b/src/entities/currency.entity.ts
index 321b087..afcbcf0 100644
--- a/src/entities/currency.entity.ts
+++ b/src/entities/currency.entity.ts
@@ -1,10 +1,8 @@
 import { ApiProperty } from '@nestjs/swagger';
-import { Account } from '../entities/account.entity';
 import {
   Column,
   CreateDateColumn,
   Entity,
-  OneToMany,
   PrimaryGeneratedColumn,
 } from 'typeorm';
 
@@ -29,8 +27,4 @@ export class Currency {
   @ApiProperty()
   @Column({ type: 'char' })
   symbol: string;
-
-  @ApiProperty({ type: () => Account })
-  @OneToMany(() => Account, (account) => account.currency, { cascade: true })
-  accounts: Account[];
 }
diff --git a/src/entities/transaction.entity.ts b/src/entities/transaction.entity.ts
index bc9f13a..3392f96 100644
--- a/src/entities/transaction.entity.ts
+++ b/src/entities/transaction.entity.ts
@@ -28,10 +28,10 @@ export class Transaction {
   date: Date;
 
   @ApiProperty({ type: () => Account })
-  @ManyToOne(() => Account, (account) => account.transactions)
+  @ManyToOne(() => Account)
   account: Account;
 
   @ApiProperty({ type: () => Category })
-  @ManyToOne(() => Category, (category) => category.transactions)
+  @ManyToOne(() => Category)
   category: Category;
 }
diff --git a/src/entities/user.entity.ts b/src/entities/user.entity.ts
index a6314c0..8cb8083 100644
--- a/src/entities/user.entity.ts
+++ b/src/entities/user.entity.ts
@@ -5,10 +5,8 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   UpdateDateColumn,
-  OneToMany,
 } from 'typeorm';
-import { Account } from '../entities/account.entity';
-import { Category } from '../entities/category.entity';
+import { IsEmail } from 'class-validator';
 
 @Entity()
 export class User {
@@ -25,6 +23,7 @@ export class User {
   updatedAt: Date;
 
   @ApiProperty()
+  @IsEmail()
   @Column()
   email: string;
 
@@ -35,12 +34,4 @@ export class User {
   @ApiProperty()
   @Column()
   password: string;
-
-  @ApiProperty({ type: () => Account })
-  @OneToMany(() => Account, (account) => account.user, { cascade: true })
-  accounts: Account[];
-
-  @ApiProperty({ type: () => Category })
-  @OneToMany(() => Category, (category) => category.user, { cascade: true })
-  categories: Category[];
 }
diff --git a/src/global-exception.filter.ts b/src/global-exception.filter.ts
index baa9b79..d34cb6e 100644
--- a/src/global-exception.filter.ts
+++ b/src/global-exception.filter.ts
@@ -1,6 +1,7 @@
 import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common';
 import { Request, Response } from 'express';
 import axios from 'axios';
+import * as process from 'node:process';
 
 @Catch()
 export class GlobalExceptionFilter implements ExceptionFilter {
@@ -16,8 +17,8 @@ export class GlobalExceptionFilter implements ExceptionFilter {
     );
 
     if (process.env.NODE_ENV !== 'test') {
-      const botToken = `8043688697:AAHQUtSLrE7xZWNJIQB4RvytMBzA9qHLJFc`;
-      const chatId = -4798165321;
+      const botToken = process.env.BOT_TOKEN;
+      const chatId = process.env.CHAT_ID;
       const errorMessage =
         `⚠️ Error on ${request.method} ${request.url}\n` +
         `${exception.name}: ${exception.message}\n` +
diff --git a/src/history/history.controller.ts b/src/history/history.controller.ts
index 1d23383..27e50dc 100644
--- a/src/history/history.controller.ts
+++ b/src/history/history.controller.ts
@@ -32,7 +32,7 @@ export class HistoryController {
       }
 
       const currency = transaction.account.currency.code;
-      let amountInRUB;
+      let amountInRUB: number;
 
       try {
         if (currency != 'RUB') {
@@ -49,10 +49,7 @@ export class HistoryController {
         amountInRUB = transaction.amount;
       }
 
-      if (
-        transaction.date?.substring(5, 7) ==
-        String(now.getMonth() + 1).padStart(2, '0')
-      ) {
+      if (transaction.date.getMonth() == now.getMonth()) {
         const category = transaction.category.name;
         if (transaction.category.operationType == OperationType.EXPENSE) {
           expensesByCategory[category] =
diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts
index c8556ef..da2c9f2 100644
--- a/src/transactions/transactions.service.ts
+++ b/src/transactions/transactions.service.ts
@@ -47,22 +47,9 @@ export default class TransactionsService {
   }
 
   async findAllTransactionsByUserId(userId: number) {
-    const user = await this.userRepository.findOne({
-      where: { id: userId },
-      relations: [
-        'accounts.transactions.category',
-        'accounts.transactions.account.currency',
-      ],
+    return await this.transactionRepository.find({
+      relations: ['account', 'user'],
+      where: { account: { user: { id: userId } } },
     });
-    if (user && user.accounts != null) {
-      const transactions = user.accounts.reduce((acc, account) => {
-        if (account.transactions != null) {
-          acc.push(...account.transactions);
-        }
-        return acc;
-      }, []);
-
-      return transactions;
-    }
   }
 }
diff --git a/src/util/fakers.ts b/src/util/fakers.ts
new file mode 100644
index 0000000..9a37994
--- /dev/null
+++ b/src/util/fakers.ts
@@ -0,0 +1,32 @@
+import { User } from '../entities/user.entity';
+import { AccountType } from '../enums/AccountType';
+import type { Account } from '../entities/account.entity';
+import type { Currency } from '../entities/currency.entity';
+
+export const fakeUser: User = {
+  id: 111,
+  createdAt: new Date(),
+  updatedAt: new Date(),
+  email: 'email@gmail.com',
+  name: 'name',
+  password: 'password',
+};
+
+export const fakeCurrency: Currency = {
+  id: 222,
+  createdAt: new Date(),
+  name: 'Российский Рубль',
+  code: 'RUB',
+  symbol: '₽',
+};
+
+export const fakeAccount: Account = {
+  id: 222,
+  createdAt: new Date(),
+  updatedAt: new Date(),
+  title: 'title',
+  isActive: true,
+  accountType: AccountType.CURRENT,
+  user: fakeUser,
+  currency: fakeCurrency,
+};


Reviews:
user: login: AnnemariaRe
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-20T14:17:50Z
user: login: Onexx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-20T15:03:30Z
user: login: AnnemariaRe
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-21T08:49:44Z
user: login: linuxovich
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T09:59:31Z
user: login: linuxovich
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T10:01:18Z
user: login: linuxovich
body: Пофиксить два коммента и можно заливать
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-21T10:02:24Z
user: login: Onexx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T14:04:44Z
user: login: Onexx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T14:07:25Z

Review comments:
diff_hunk: @@ -13,6 +13,9 @@ services:
       - /app/node_modules
     environment:
       - NODE_ENV=development
+      - DB_URL=postgres://user:pg_password@db/postgres_db
path: docker-compose.yml
user: login: AnnemariaRe
body: Почему тут дублируются значения из .env?
Можно же прописать:
```
env_file:
      - .env
```
created_at: 2025-05-20T14:04:07Z
author_association: OWNER
diff_hunk: @@ -2,19 +2,19 @@ import { Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { User } from 'src/entities/user.entity';
 import { Repository } from 'typeorm';
+import type { Category } from '../entities/category.entity';
 
 @Injectable()
 export default class CategoriesService {
   constructor(
     @InjectRepository(User)
path: src/categories/catigories.service.ts
user: login: AnnemariaRe
body: тут должен же быть `Category`?
created_at: 2025-05-20T14:09:15Z
author_association: OWNER
diff_hunk: @@ -13,6 +13,9 @@ services:
       - /app/node_modules
     environment:
       - NODE_ENV=development
+      - DB_URL=postgres://user:pg_password@db/postgres_db
path: docker-compose.yml
user: login: Onexx
body: .env в корне репы - для локальной разработки, docker-compose - для прода. Значения в них не обязаны дублировать друг друга. Когда я локально дебагаю, например, с кастомным DB_URL, мне хочется все еще иметь возможность собрать весь стек без изменения моих конфигов
created_at: 2025-05-20T15:03:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,4 @@
+DB_URL=DB_URL=postgres://user:pg_password@db/postgres_db
path: .env
user: login: AnnemariaRe
body: Дублируется DB_URL
created_at: 2025-05-21T08:49:43Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,4 @@
+DB_URL=DB_URL=postgres://user:pg_password@db/postgres_db
path: .env
user: login: linuxovich
body: соглы 
created_at: 2025-05-21T09:59:31Z
author_association: COLLABORATOR
diff_hunk: @@ -2,19 +2,19 @@ import { Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { User } from 'src/entities/user.entity';
 import { Repository } from 'typeorm';
+import type { Category } from '../entities/category.entity';
 
 @Injectable()
 export default class CategoriesService {
   constructor(
     @InjectRepository(User)
path: src/categories/catigories.service.ts
user: login: linuxovich
body: А вОт в НеОвИмЕ тАкоЕ нЕ сЛуЧиЛоСь Бы)))

created_at: 2025-05-21T10:01:18Z
author_association: COLLABORATOR
diff_hunk: @@ -2,19 +2,19 @@ import { Injectable } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { User } from 'src/entities/user.entity';
 import { Repository } from 'typeorm';
+import type { Category } from '../entities/category.entity';
 
 @Injectable()
 export default class CategoriesService {
   constructor(
     @InjectRepository(User)
path: src/categories/catigories.service.ts
user: login: Onexx
body: Fixed. Про неовим не соглашусь, пожалуй
created_at: 2025-05-21T14:04:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,4 @@
+DB_URL=DB_URL=postgres://user:pg_password@db/postgres_db
path: .env
user: login: Onexx
body: Fixed
created_at: 2025-05-21T14:07:25Z
author_association: COLLABORATOR

PR comments:
user: login: Onexx
created_at: 2025-05-20T08:08:08Z
body: Пришлось перегенерить все миграции тк при раскатке на пустую базу они не работали - в первой миграции нужна была сущность, создаваемая во второй
author_association: COLLABORATOR
user: login: Onexx
created_at: 2025-05-20T08:09:57Z
body: Миграции, насыщающие бд данными прогонялись не последними, а где-то в середине. Сделал у них timestamp максимальным, чтобы точно последними прогонялись
author_association: COLLABORATOR