ratelimit.ts (2820B)
1 import { RateLimitRepository } from "#core/application/oauth2/ratelimit_repository.ts"; 2 import { 3 EntityLocked, 4 EntityNotFound, 5 } from "#core/application/repository_error.ts"; 6 import { RateLimit } from "#core/domain/rate_limit.ts"; 7 import { InvalidUUID } from "#core/domain/uuid.ts"; 8 import { 9 RateLimitDto, 10 mapFromRateLimit, 11 mapToRateLimit, 12 } from "#infrastructure/memory/mapper/ratelimit.ts"; 13 import { mapError } from "#infrastructure/postgres/error.ts"; 14 import { Pool } from "$postgres"; 15 16 export class PostgresRateLimitRepositoryAdapter 17 implements RateLimitRepository { 18 constructor(readonly pool: Pool) { 19 } 20 21 async findOrCreate(key: string): Promise<RateLimit> { 22 try { 23 const connection = await this.pool.connect(); 24 try { 25 const result = await connection.queryObject<RateLimitDto>` 26 select * 27 from "ratelimit" 28 where key = ${key.toString()} 29 limit 1; 30 `; 31 if (result.rowCount !== 1) { 32 return new RateLimit(key) 33 } 34 return mapToRateLimit(result.rows[0]); 35 } finally { 36 connection.release(); 37 } 38 } catch (error) { 39 if (error instanceof InvalidUUID) { 40 throw new EntityNotFound(key.toString()); 41 } 42 throw mapError(error); 43 } 44 } 45 46 async store(rateLimit: RateLimit): Promise<void> { 47 try { 48 const dto = mapFromRateLimit(rateLimit); 49 const connection = await this.pool.connect(); 50 const transaction = connection.createTransaction( 51 `txn_${dto.key}_${dto.version}`, 52 ); 53 try { 54 await transaction.begin(); 55 dto.version++; 56 if (dto.version === 1) { 57 await transaction.queryArray` 58 insert into "ratelimit" ( 59 "key", "count", "expire", "version" 60 ) values ( 61 ${dto.key}, ${dto.count}, ${dto.expire}, ${dto.version} 62 ); 63 `; 64 await transaction.commit(); 65 rateLimit.version = dto.version; 66 return; 67 } 68 const result = await transaction.queryObject<{ version: number }>` 69 update "ratelimit" set 70 "count" = ${dto.count}, 71 "expire" = ${dto.expire}, 72 "version" = "version" + 1 73 where "key" = ${dto.key} 74 returning version; 75 `; 76 if (result.rowCount !== 1) { 77 transaction.rollback(); 78 throw new EntityNotFound(dto.key); 79 } 80 if (result.rows[0].version === dto.version) { 81 await transaction.commit(); 82 rateLimit.version = dto.version; 83 return; 84 } 85 transaction.rollback(); 86 throw new EntityLocked(); 87 } finally { 88 connection.release(); 89 } 90 } catch (error) { 91 throw mapError(error); 92 } 93 } 94 }