oauth2_flow.ts (3820B)
1 import { OAuth2FlowRepository } from "#core/application/oauth2/flow_repository.ts"; 2 import { 3 EntityLocked, 4 EntityNotFound, 5 } from "#core/application/repository_error.ts"; 6 import { OAuth2Flow } from "#core/domain/oauth2flow.ts"; 7 import { Token } from "#core/domain/token.ts"; 8 import { UUID } from "#core/domain/uuid.ts"; 9 import { 10 mapFromOAuth2Flow, 11 mapToOAuth2Flow, 12 OAuth2FlowDto, 13 } from "#infrastructure/memory/mapper/oauth2flow.ts"; 14 import { mapError } from "#infrastructure/postgres/error.ts"; 15 import { Pool } from "$postgres"; 16 17 export class PostgresOAuth2FlowRepositoryAdapter 18 implements OAuth2FlowRepository { 19 constructor(readonly pool: Pool) { 20 } 21 22 async find(uuid: UUID): Promise<OAuth2Flow> { 23 try { 24 const connection = await this.pool.connect(); 25 try { 26 const result = await connection.queryObject< 27 OAuth2FlowDto 28 >`select * from "oauth2flow" where "uuid" = ${uuid.toString()} limit 1;`; 29 if (result.rowCount !== 1) { 30 throw new EntityNotFound(uuid.toString()); 31 } 32 return mapToOAuth2Flow(result.rows[0]); 33 } finally { 34 connection.release(); 35 } 36 } catch (error) { 37 throw mapError(error); 38 } 39 } 40 41 async findByToken(token: Token): Promise<OAuth2Flow> { 42 try { 43 const connection = await this.pool.connect(); 44 try { 45 const result = await connection.queryObject< 46 OAuth2FlowDto 47 >`select * from "oauth2flow" where "token" = ${token.toString()} limit 1;`; 48 if (result.rowCount !== 1) { 49 throw new EntityNotFound(token.toString()); 50 } 51 return mapToOAuth2Flow(result.rows[0]); 52 } finally { 53 connection.release(); 54 } 55 } catch (error) { 56 throw mapError(error); 57 } 58 } 59 60 async store(flow: OAuth2Flow): Promise<void> { 61 try { 62 const dto = mapFromOAuth2Flow(flow); 63 const connection = await this.pool.connect(); 64 const transaction = connection.createTransaction( 65 `txn_${dto.uuid}_${dto.version}`, 66 ); 67 try { 68 await transaction.begin(); 69 await transaction 70 .queryArray`delete from "oauth2flow" where created < (current_timestamp - interval '1 day');`; 71 72 dto.version++; 73 if (dto.version === 1) { 74 await transaction.queryObject<void>` 75 insert into "oauth2flow" ( 76 "uuid", "clientId", "scope", "state", 77 "resourceOwner", "token", "tokenExpire", 78 "version" 79 ) values ( 80 ${dto.uuid}, ${dto.clientId}, ${dto.scope}, ${dto.state}, 81 ${dto.resourceOwner}, ${dto.token}, ${dto.tokenExpire}, 82 ${dto.version} 83 ); 84 `; 85 await transaction.commit(); 86 flow.version = dto.version; 87 return; 88 } 89 const result = await transaction.queryObject<{ version: number }>` 90 update "oauth2flow" set 91 "uuid" = ${dto.uuid}, 92 "clientId" = ${dto.clientId}, 93 "scope" = ${dto.scope}, 94 "state" = ${dto.state}, 95 "resourceOwner" = ${dto.resourceOwner}, 96 "token" = ${dto.token}, 97 "tokenExpire" = ${dto.tokenExpire}, 98 "version" = "version" + 1 99 where uuid = ${dto.uuid} 100 returning version; 101 `; 102 if (result.rowCount !== 1) { 103 transaction.rollback(); 104 throw new EntityNotFound(dto.uuid); 105 } 106 if (result.rows[0].version === dto.version) { 107 await transaction.commit(); 108 flow.version = result.rows[0].version; 109 return; 110 } 111 transaction.rollback(); 112 throw new EntityLocked(); 113 } finally { 114 connection.release(); 115 } 116 } catch (error) { 117 throw mapError(error); 118 } 119 } 120 }