From 7c1a24e2a04d48c841430bc906bc8fda24991230 Mon Sep 17 00:00:00 2001 From: mijinummi Date: Thu, 26 Mar 2026 21:53:02 +0100 Subject: [PATCH] feat(database): implement global transaction middleware with TypeORM for ACID integrity --- backend/src/app.module.ts | 18 +++++---- .../transaction/transaction.logger.ts | 14 +++++++ .../transaction/transaction.manager.ts | 36 ++++++++++++++++++ .../transaction/transaction.middleware.ts | 37 +++++++++++++++++++ 4 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 backend/src/middleware/transaction/transaction.logger.ts create mode 100644 backend/src/middleware/transaction/transaction.manager.ts create mode 100644 backend/src/middleware/transaction/transaction.middleware.ts diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 7ef8089..d4e6b17 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { EventEmitterModule } from '@nestjs/event-emitter'; @@ -16,10 +16,8 @@ import { PuzzlesModule } from './puzzles/puzzles.module'; import { QuestsModule } from './quests/quests.module'; import { StreakModule } from './streak/strerak.module'; import { CategoriesModule } from './categories/categories.module'; - -// const ENV = process.env.NODE_ENV; -// console.log('NODE_ENV:', process.env.NODE_ENV); -// console.log('ENV:', ENV); +import { TransactionMiddleware } from './middleware/transaction/transaction.middleware'; +import { TransactionLogger } from './middleware/transaction/transaction.logger'; @Module({ imports: [ @@ -81,10 +79,14 @@ import { CategoriesModule } from './categories/categories.module'; CommonModule, RedisModule, BlockchainModule, - ProgressModule, CategoriesModule, ], controllers: [AppController], - providers: [AppService], + providers: [AppService, TransactionLogger], }) -export class AppModule {} +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + // Apply transaction middleware globally + consumer.apply(TransactionMiddleware).forRoutes('*'); + } +} diff --git a/backend/src/middleware/transaction/transaction.logger.ts b/backend/src/middleware/transaction/transaction.logger.ts new file mode 100644 index 0000000..2e92309 --- /dev/null +++ b/backend/src/middleware/transaction/transaction.logger.ts @@ -0,0 +1,14 @@ +import { Injectable, Logger } from '@nestjs/common'; + +@Injectable() +export class TransactionLogger { + private readonly logger = new Logger('Transaction'); + + log(message: string) { + this.logger.log(message); + } + + error(message: string, error: any) { + this.logger.error(`${message}: ${error.message}`, error.stack); + } +} diff --git a/backend/src/middleware/transaction/transaction.manager.ts b/backend/src/middleware/transaction/transaction.manager.ts new file mode 100644 index 0000000..8175ec7 --- /dev/null +++ b/backend/src/middleware/transaction/transaction.manager.ts @@ -0,0 +1,36 @@ +import { DataSource, QueryRunner } from 'typeorm'; + +export class TransactionManager { + private queryRunner: QueryRunner; + + constructor(private readonly dataSource: DataSource) { + this.queryRunner = this.dataSource.createQueryRunner(); + } + + async startTransaction(isolation: 'READ COMMITTED' | 'REPEATABLE READ' | 'SERIALIZABLE' = 'READ COMMITTED') { + await this.queryRunner.connect(); + await this.queryRunner.startTransaction(isolation); + } + + async commitTransaction() { + await this.queryRunner.commitTransaction(); + await this.queryRunner.release(); + } + + async rollbackTransaction() { + await this.queryRunner.rollbackTransaction(); + await this.queryRunner.release(); + } + + async createSavepoint(name: string) { + await this.queryRunner.query(`SAVEPOINT ${name}`); + } + + async rollbackToSavepoint(name: string) { + await this.queryRunner.query(`ROLLBACK TO SAVEPOINT ${name}`); + } + + getManager() { + return this.queryRunner.manager; + } +} diff --git a/backend/src/middleware/transaction/transaction.middleware.ts b/backend/src/middleware/transaction/transaction.middleware.ts new file mode 100644 index 0000000..11bb680 --- /dev/null +++ b/backend/src/middleware/transaction/transaction.middleware.ts @@ -0,0 +1,37 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import { DataSource } from 'typeorm'; +import { TransactionManager } from './transaction.manager'; +import { TransactionLogger } from './transaction.logger'; + +@Injectable() +export class TransactionMiddleware implements NestMiddleware { + constructor(private readonly dataSource: DataSource, private readonly logger: TransactionLogger) {} + + async use(req: Request, res: Response, next: NextFunction) { + const manager = new TransactionManager(this.dataSource); + + try { + await manager.startTransaction(); + + // Attach transaction manager to request for manual control if needed + (req as any).transactionManager = manager; + + res.on('finish', async () => { + if (res.statusCode >= 200 && res.statusCode < 400) { + await manager.commitTransaction(); + this.logger.log('Transaction committed successfully'); + } else { + await manager.rollbackTransaction(); + this.logger.log('Transaction rolled back due to error'); + } + }); + + next(); + } catch (error) { + await manager.rollbackTransaction(); + this.logger.error('Transaction failed', error); + next(error); + } + } +}