import HttpStatus from '@xpring-eng/http-status' ; import { Request , Response , NextFunction } from 'express' ; import config , { adminApiVersions } from '../config' ; import { getAllAddressInfoFromDatabase , getAllVerifiedAddressInfoFromDatabase , getIdentityKeyFromDatabase , } from '../data-access/payIds' ; import { insertUser , replaceUser , removeUser , replaceUserPayId , checkUserExistence , } from '../data-access/users' ; import { formatPaymentInfo } from '../services/basePayId' ; import parseAllAddresses from '../services/users' ; import { LookupError , LookupErrorType , ParseError , ParseErrorType , } from '../utils/errors' ; param req param res param next throws throws export async function getUser ( req , res , next ) { const payId = req . params . payId . toLowerCase ( ) ; if ( ! payId ) { throw new ParseError ( 'A `payId` must be provided in the path. A well-formed API call would look like `GET /users/alice$xpring.money`.' , ParseErrorType . MissingPayId ) ; } const doesUserExist = await checkUserExistence ( payId ) ; if ( ! doesUserExist ) { throw new LookupError ( `No information could be found for the PayID ${ payId }.` , LookupErrorType . Unknown ) ; } const addresses = await getAllAddressInfoFromDatabase ( payId ) ; const verifiedAddresses = await getAllVerifiedAddressInfoFromDatabase ( payId ) ; const identityKey = await getIdentityKeyFromDatabase ( payId ) ; const requestVersion = String ( req . get ( 'PayID-API-Version' ) ) ; if ( requestVersion < adminApiVersions [ 1 ] ) { res . locals . response = { payId , ... ( identityKey && { identityKey } ) , addresses , verifiedAddresses , } ; } else { res . locals . response = formatPaymentInfo ( addresses , verifiedAddresses , identityKey , config . app . payIdVersion , payId ) ; } next ( ) ; } param req param res param next throws export async function postUser ( req , res , next ) { const rawPayId = req . body . payId ; if ( ! rawPayId || typeof rawPayId !== 'string' ) { throw new ParseError ( 'A `payId` must be provided in the request body.' , ParseErrorType . MissingPayId ) ; } const payId = rawPayId . toLowerCase ( ) ; const [ allAddresses , identityKey ] = parseAllAddresses ( req . body . addresses , req . body . verifiedAddresses , req . body . identityKey , String ( req . get ( 'PayID-API-Version' ) ) ) ; await insertUser ( payId , allAddresses , identityKey ) ; res . locals . status = HttpStatus . Created ; res . locals . payId = payId ; next ( ) ; } param req param res param next throws export async function putUser ( req , res , next ) { const rawPayId = req . params . payId ; const rawNewPayId = req . body ?. payId ; if ( ! rawPayId ) { throw new ParseError ( 'A `payId` must be provided in the path. A well-formed API call would look like `PUT /users/alice$xpring.money`.' , ParseErrorType . MissingPayId ) ; } if ( ! rawNewPayId || typeof rawNewPayId !== 'string' ) { throw new ParseError ( 'A `payId` must be provided in the request body.' , ParseErrorType . MissingPayId ) ; } if ( ! rawPayId . includes ( '$' ) || ! rawNewPayId . includes ( '$' ) ) { throw new ParseError ( 'Bad input. PayIDs must contain a "$"' , ParseErrorType . InvalidPayId ) ; } if ( ( rawPayId . match ( /\$/gu ) || [ ] ) . length !== 1 || ( rawNewPayId . match ( /\$/gu ) || [ ] ) . length !== 1 ) { throw new ParseError ( 'Bad input. PayIDs must contain only one "$"' , ParseErrorType . InvalidPayId ) ; } const requestVersion = String ( req . get ( 'PayID-API-Version' ) ) ; const payId = rawPayId . toLowerCase ( ) ; const newPayId = rawNewPayId . toLowerCase ( ) ; const [ allAddresses , identityKey ] = parseAllAddresses ( req . body . addresses , req . body . verifiedAddresses , req . body . identityKey , String ( req . get ( 'PayID-API-Version' ) ) ) ; let updatedAddresses = await replaceUser ( payId , newPayId , allAddresses , identityKey ) ; if ( updatedAddresses === null ) { updatedAddresses = await insertUser ( newPayId , allAddresses , identityKey ) ; res . locals . status = HttpStatus . Created ; res . locals . payId = newPayId ; } if ( requestVersion < adminApiVersions [ 1 ] ) { const addresses = updatedAddresses . filter ( ( address ) => ! address . identityKeySignature ) . map ( ( address ) => ( { paymentNetwork : address . paymentNetwork , environment : address . environment , details : address . details , } ) ) ; const verifiedAddresses = updatedAddresses . filter ( ( address ) => Boolean ( address . identityKeySignature ) ) ; res . locals . response = { payId : newPayId , addresses , verifiedAddresses , } ; } next ( ) ; } param req param res param next throws export async function deleteUser ( req , res , next ) { const payId = req . params . payId . toLowerCase ( ) ; if ( ! payId ) { throw new ParseError ( 'A PayID must be provided in the path. A well-formed API call would look like `DELETE /users/alice$xpring.money`.' , ParseErrorType . MissingPayId ) ; } await removeUser ( payId ) ; res . locals . status = HttpStatus . NoContent ; next ( ) ; } param req param res param next throws throws export async function patchUserPayId ( req , res , next ) { const rawOldPayId = req . params . payId ; if ( ! rawOldPayId ) { throw new ParseError ( 'A `payId` must be provided in the path. A well-formed API call would look like `PATCH /users/alice$xpring.money`.' , ParseErrorType . MissingPayId ) ; } const rawNewPotentialPayId = req . body . payId ; if ( ! rawNewPotentialPayId || typeof rawNewPotentialPayId !== 'string' ) { throw new ParseError ( 'A `payId` must be provided in the request body.' , ParseErrorType . MissingPayId ) ; } if ( ! rawOldPayId . includes ( '$' ) || ! rawNewPotentialPayId . includes ( '$' ) ) { throw new ParseError ( 'Bad input. PayIDs must contain a "$"' , ParseErrorType . InvalidPayId ) ; } if ( ( rawOldPayId . match ( /\$/gu ) || [ ] ) . length !== 1 || ( rawNewPotentialPayId . match ( /\$/gu ) || [ ] ) . length !== 1 ) { throw new ParseError ( 'Bad input. PayIDs must contain only one "$"' , ParseErrorType . InvalidPayId ) ; } const newPayId = rawNewPotentialPayId . toLowerCase ( ) ; const oldPayId = rawOldPayId . toLowerCase ( ) ; const verifiedAddresses = await getAllVerifiedAddressInfoFromDatabase ( oldPayId ) ; if ( verifiedAddresses . length > 0 ) { throw new ParseError ( 'Cannot PATCH a PayID with verified addresses, as that would break the address signatures. Please use the PUT endpoint to update this PayID.' , ParseErrorType . IncompatibleRequestMethod ) ; } const account = await replaceUserPayId ( oldPayId , newPayId ) ; if ( ! account ) { throw new LookupError ( `The PayID ${ oldPayId } doesn't exist.` , LookupErrorType . MissingPayId ) ; } res . locals . status = HttpStatus . Created ; res . locals . payId = newPayId ; next ( ) ; }