Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler"
import { AdminModule } from "./admin/admin.module"
import { AuditModule } from "./audit/audit.module"
import { AuthModule } from "./auth/auth.module"
import { DatabaseModule } from "./database/database.module"
import { GatewaysModule } from "./gateways/gateways.module"
import { HealthModule } from "./health/health.module"
import { MetricsModule } from "./metrics/metrics.module"
Expand All @@ -19,6 +20,7 @@ import { TagsModule } from "./tags/tags.module"
limit: parseInt(process.env.THROTTLE_LIMIT ?? "100"),
},
]),
DatabaseModule,
AdminModule,
AuditModule,
AuthModule,
Expand Down
5 changes: 3 additions & 2 deletions api/src/audit/audit.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Injectable } from "@nestjs/common"
import { Inject, Injectable } from "@nestjs/common"
import { Pool } from "pg"
import { PG_POOL } from "../database/database.module"

@Injectable()
export class AuditService {
private pool = new Pool({ connectionString: process.env.DATABASE_URL })
constructor(@Inject(PG_POOL) private readonly pool: Pool) {}

async log(userId: number | null, action: string, ip: string) {
await this.pool.query(
Expand Down
5 changes: 2 additions & 3 deletions api/src/auth/password-reset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Cache } from "cache-manager"
import { Pool } from "pg"
import * as bcrypt from "bcrypt"
import * as crypto from "crypto"
import { PG_POOL } from "../database/database.module"
import { UsersRepository } from "./users.repository"

const PASSWORD_RESET_TOKEN_BYTES = 32
Expand All @@ -14,14 +15,12 @@ const BCRYPT_ROUNDS = 12

@Injectable()
export class PasswordResetService {
private readonly pool = new Pool({
connectionString: process.env.DATABASE_URL,
})
private readonly logger = new Logger(PasswordResetService.name)

constructor(
private readonly usersRepository: UsersRepository,
@Inject(CACHE_MANAGER) private readonly cache: Cache,
@Inject(PG_POOL) private readonly pool: Pool,
) {}

async sendResetToken(email: string): Promise<void> {
Expand Down
5 changes: 3 additions & 2 deletions api/src/auth/users.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from "@nestjs/common"
import { Inject, Injectable } from "@nestjs/common"
import { Pool } from "pg"
import { PG_POOL } from "../database/database.module"

export interface User {
id: number
Expand All @@ -18,7 +19,7 @@ export interface User {
*/
@Injectable()
export class UsersRepository {
private pool = new Pool({ connectionString: process.env.DATABASE_URL })
constructor(@Inject(PG_POOL) private readonly pool: Pool) {}

async findByEmail(email: string): Promise<User | null> {
const { rows } = await this.pool.query(
Expand Down
29 changes: 29 additions & 0 deletions api/src/database/database.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Test, TestingModule } from "@nestjs/testing"
import { DatabaseModule, PG_POOL } from "./database.module"
import { Pool } from "pg"

describe("DatabaseModule", () => {
let module: TestingModule

beforeEach(async () => {
module = await Test.createTestingModule({
imports: [DatabaseModule],
}).compile()
})

afterEach(async () => {
await module.close()
})

it("provides PG_POOL token as a Pool instance", () => {
const pool = module.get<Pool>(PG_POOL)
expect(pool).toBeDefined()
expect(pool).toBeInstanceOf(Pool)
})

it("provides the same Pool instance on repeated resolution (singleton)", () => {
const pool1 = module.get<Pool>(PG_POOL)
const pool2 = module.get<Pool>(PG_POOL)
expect(pool1).toBe(pool2)
})
})
17 changes: 17 additions & 0 deletions api/src/database/database.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Global, Module } from "@nestjs/common"
import { Pool } from "pg"
import { env } from "../config/env"

export const PG_POOL = "PG_POOL"

@Global()
@Module({
providers: [
{
provide: PG_POOL,
useFactory: (): Pool => new Pool({ connectionString: env.DATABASE_URL }),
},
],
exports: [PG_POOL],
})
export class DatabaseModule {}
13 changes: 6 additions & 7 deletions api/src/health/database.health-indicator.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from "@nestjs/terminus"
import { Injectable, OnModuleDestroy } from "@nestjs/common"
import { Inject, Injectable } from "@nestjs/common"
import { Pool } from "pg"
import { PG_POOL } from "../database/database.module"

@Injectable()
export class DatabaseHealthIndicator extends HealthIndicator implements OnModuleDestroy {
private readonly pool = new Pool({ connectionString: process.env.DATABASE_URL })
export class DatabaseHealthIndicator extends HealthIndicator {
constructor(@Inject(PG_POOL) private readonly pool: Pool) {
super()
}

async isHealthy(key: string): Promise<HealthIndicatorResult> {
try {
Expand All @@ -14,8 +17,4 @@ export class DatabaseHealthIndicator extends HealthIndicator implements OnModule
throw new HealthCheckError("database", error)
}
}

async onModuleDestroy(): Promise<void> {
await this.pool.end()
}
}
12 changes: 3 additions & 9 deletions api/src/streams/repository/streams-db.repository.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {
Inject,
Injectable,
InternalServerErrorException,
Logger,
ServiceUnavailableException,
} from "@nestjs/common"
import { Pool } from "pg"
import { env } from "../../config/env"
import { PG_POOL } from "../../database/database.module"
import { Stream } from "../stream.entity"

/**
Expand All @@ -19,16 +20,9 @@ import { Stream } from "../stream.entity"
*/
@Injectable()
export class StreamsDbRepository {
private readonly pool: Pool
private readonly logger = new Logger(StreamsDbRepository.name)

constructor() {
this.pool = new Pool({ connectionString: env.DATABASE_URL })

this.pool.on("error", (err) => {
this.logger.error("Unexpected PostgreSQL pool error", err.stack)
})
}
constructor(@Inject(PG_POOL) private readonly pool: Pool) {}

/** Map a raw DB row to the Stream entity shape. */
private rowToStream(row: Record<string, unknown>): Stream {
Expand Down
12 changes: 3 additions & 9 deletions api/src/tags/repository/tags-db.repository.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {
Inject,
Injectable,
InternalServerErrorException,
Logger,
ServiceUnavailableException,
} from "@nestjs/common"
import { Pool } from "pg"
import { env } from "../../config/env"
import { PG_POOL } from "../../database/database.module"
import { StreamTag, Tag } from "../tag.entity"

/**
Expand All @@ -19,16 +20,9 @@ import { StreamTag, Tag } from "../tag.entity"
*/
@Injectable()
export class TagsDbRepository {
private readonly pool: Pool
private readonly logger = new Logger(TagsDbRepository.name)

constructor() {
this.pool = new Pool({ connectionString: env.DATABASE_URL })

this.pool.on("error", (err) => {
this.logger.error("Unexpected PostgreSQL pool error", err.stack)
})
}
constructor(@Inject(PG_POOL) private readonly pool: Pool) {}

/** Map a raw DB row to the Tag entity shape. */
private rowToTag(row: Record<string, unknown>): Tag {
Expand Down
Loading