import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqlSchema from "effect/unstable/sql/SqlSchema"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import { toPersistenceDecodeError, toPersistenceSqlError, type AuthPairingLinkRepositoryError, } from "../Errors.ts"; import { AuthPairingLinkRecord, AuthPairingLinkRepository, type AuthPairingLinkRepositoryShape, ConsumeAuthPairingLinkInput, CreateAuthPairingLinkInput, GetAuthPairingLinkByCredentialInput, ListActiveAuthPairingLinksInput, RevokeAuthPairingLinkInput, } from "../Services/AuthPairingLinks.ts"; function toPersistenceSqlOrDecodeError(sqlOperation: string, decodeOperation: string) { return (cause: unknown): AuthPairingLinkRepositoryError => Schema.isSchemaError(cause) ? toPersistenceDecodeError(decodeOperation)(cause) : toPersistenceSqlError(sqlOperation)(cause); } const makeAuthPairingLinkRepository = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; const createPairingLinkRow = SqlSchema.void({ Request: CreateAuthPairingLinkInput, execute: (input) => sql` INSERT INTO auth_pairing_links ( id, credential, method, scopes, subject, label, created_at, expires_at, consumed_at, revoked_at ) VALUES ( ${input.id}, ${input.credential}, ${input.method}, ${JSON.stringify(input.scopes)}, ${input.subject}, ${input.label}, ${input.createdAt}, ${input.expiresAt}, NULL, NULL ) `, }); const consumeAvailablePairingLinkRow = SqlSchema.findOneOption({ Request: ConsumeAuthPairingLinkInput, Result: AuthPairingLinkRecord, execute: ({ credential, consumedAt, now }) => sql` UPDATE auth_pairing_links SET consumed_at = ${consumedAt} WHERE credential = ${credential} AND revoked_at IS NULL AND consumed_at IS NULL AND expires_at > ${now} RETURNING id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" `, }); const listActivePairingLinkRows = SqlSchema.findAll({ Request: ListActiveAuthPairingLinksInput, Result: AuthPairingLinkRecord, execute: ({ now }) => sql` SELECT id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" FROM auth_pairing_links WHERE revoked_at IS NULL AND consumed_at IS NULL AND expires_at > ${now} ORDER BY created_at DESC, id DESC `, }); const revokePairingLinkRow = SqlSchema.findAll({ Request: RevokeAuthPairingLinkInput, Result: Schema.Struct({ id: Schema.String }), execute: ({ id, revokedAt }) => sql` UPDATE auth_pairing_links SET revoked_at = ${revokedAt} WHERE id = ${id} AND revoked_at IS NULL AND consumed_at IS NULL RETURNING id AS "id" `, }); const getPairingLinkRowByCredential = SqlSchema.findOneOption({ Request: GetAuthPairingLinkByCredentialInput, Result: AuthPairingLinkRecord, execute: ({ credential }) => sql` SELECT id AS "id", credential AS "credential", method AS "method", scopes AS "scopes", subject AS "subject", label AS "label", created_at AS "createdAt", expires_at AS "expiresAt", consumed_at AS "consumedAt", revoked_at AS "revokedAt" FROM auth_pairing_links WHERE credential = ${credential} `, }); const create: AuthPairingLinkRepositoryShape["create"] = (input) => createPairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.create:query", "AuthPairingLinkRepository.create:encodeRequest", ), ), ); const consumeAvailable: AuthPairingLinkRepositoryShape["consumeAvailable"] = (input) => consumeAvailablePairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.consumeAvailable:query", "AuthPairingLinkRepository.consumeAvailable:decodeRow", ), ), ); const listActive: AuthPairingLinkRepositoryShape["listActive"] = (input) => listActivePairingLinkRows(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.listActive:query", "AuthPairingLinkRepository.listActive:decodeRows", ), ), ); const revoke: AuthPairingLinkRepositoryShape["revoke"] = (input) => revokePairingLinkRow(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.revoke:query", "AuthPairingLinkRepository.revoke:decodeRows", ), ), Effect.map((rows) => rows.length > 0), ); const getByCredential: AuthPairingLinkRepositoryShape["getByCredential"] = (input) => getPairingLinkRowByCredential(input).pipe( Effect.mapError( toPersistenceSqlOrDecodeError( "AuthPairingLinkRepository.getByCredential:query", "AuthPairingLinkRepository.getByCredential:decodeRow", ), ), ); return { create, consumeAvailable, listActive, revoke, getByCredential, } satisfies AuthPairingLinkRepositoryShape; }); export const AuthPairingLinkRepositoryLive = Layer.effect( AuthPairingLinkRepository, makeAuthPairingLinkRepository, );