Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/server/infrastructure/db/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { sql } from "drizzle-orm";

export const countAll = sql<number>`count(*)::int`;
4 changes: 2 additions & 2 deletions src/server/infrastructure/di/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { DeleteUserUseCase } from "../../core/use-cases/admin/delete-user";
import { GetAllRolesUseCase } from "../../core/use-cases/admin/get-all-roles";
import { AssignRoleUseCase } from "../../core/use-cases/admin/assign-role";
import { RemoveRoleUseCase } from "../../core/use-cases/admin/remove-role";
import { InMemoryRateLimiterService } from "../services/in-memory-rate-limiter.service";
import { RedisRateLimiterService } from "../services/redis-rate-limiter.service";
import { redis } from "../redis";
import { RedisTokenBlacklistService } from "../services/redis-token-blacklist.service";
import { TockTokenService } from "../services/token.service";
Expand Down Expand Up @@ -75,7 +75,7 @@ container.register({
removeRoleUseCase: asClass(RemoveRoleUseCase).singleton(),

// Services
rateLimiterService: asClass(InMemoryRateLimiterService).singleton(),
rateLimiterService: asClass(RedisRateLimiterService).singleton(),
tokenService: asClass(TockTokenService).singleton(),
healthCheckService: asClass(DrizzleHealthCheckService).singleton(),
});
7 changes: 4 additions & 3 deletions src/server/infrastructure/persistence/role.pg.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { eq, inArray, sql, and } from "drizzle-orm";
import { eq, inArray, and } from "drizzle-orm";
import { DB } from "../db";
import { countAll } from "../db/pagination";
import { withDbError } from "../db/with-db-error";
import {
roles,
Expand Down Expand Up @@ -37,7 +38,7 @@ export class PostgresRoleRepository implements IRoleRepository {
const { limit = 20, offset = 0 } = options;
const [rows, countResult] = await Promise.all([
this.db.select().from(roles).limit(limit).offset(offset).orderBy(roles.name),
this.db.select({ count: sql<number>`count(*)::int` }).from(roles),
this.db.select({ count: countAll }).from(roles),
]);
return {
roles: rows.map((r) => new RoleEntity(r.id, r.name, r.description, r.createdAt)),
Expand Down Expand Up @@ -109,7 +110,7 @@ export class PostgresRoleRepository implements IRoleRepository {
async countUsersWithRole(roleId: string): Promise<number> {
return withDbError("count users with role", async () => {
const result = await this.db
.select({ count: sql<number>`count(*)::int` })
.select({ count: countAll })
.from(userRoles)
.where(eq(userRoles.roleId, roleId));
return result[0]?.count ?? 0;
Expand Down
5 changes: 3 additions & 2 deletions src/server/infrastructure/persistence/user.pg.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { eq, sql } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { DB } from "../db";
import { countAll } from "../db/pagination";
import { withDbError, isDbError } from "../db/with-db-error";
import { users } from "./schema/user.schema";
import { IUserRepository } from "../../core/repositories/user.repository";
Expand Down Expand Up @@ -69,7 +70,7 @@ export class PostgresUserRepository implements IUserRepository {
const { limit = 20, offset = 0 } = options;
const [rows, countResult] = await Promise.all([
this.db.select().from(users).limit(limit).offset(offset).orderBy(users.createdAt),
this.db.select({ count: sql<number>`count(*)::int` }).from(users),
this.db.select({ count: countAll }).from(users),
]);
return {
users: rows.map(
Expand Down
37 changes: 37 additions & 0 deletions src/server/infrastructure/services/redis-rate-limiter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Redis } from "ioredis";
import { IRateLimiterService } from "../../core/services/rate-limiter.service";
import { logger } from "../logger";

const KEY_PREFIX = "ratelimit:";

export class RedisRateLimiterService implements IRateLimiterService {
constructor(private readonly redis: Redis) {}

async isAllowed(
key: string,
maxAttempts: number,
windowMs: number,
): Promise<boolean> {
try {
const redisKey = `${KEY_PREFIX}${key}`;
const count = await this.redis.incr(redisKey);

if (count === 1) {
await this.redis.pexpire(redisKey, windowMs);
}
Comment on lines +16 to +21

return count <= maxAttempts;
} catch (err) {
logger.error({ err }, "[Redis] Rate limiter check failed, allowing request");
return true;
}
}

async reset(key: string): Promise<void> {
try {
await this.redis.del(`${KEY_PREFIX}${key}`);
} catch (err) {
logger.error({ err }, "[Redis] Rate limiter reset failed");
}
}
}
Loading