import express from 'express' ; import db from '@mds-core/mds-db' ; import { pathsFor , ServerError , NotFoundError , DependencyMissingError , ValidationError , AlreadyPublishedError , InsufficientPermissionsError , BadParamsError } from '@mds-core/mds-utils' ; import { geographyValidationDetails } from '@mds-core/mds-schema-validators' ; import logger from '@mds-core/mds-logger' ; import { checkAccess , AccessTokenScopeValidator , ApiResponse , ApiRequest } from '@mds-core/mds-api-server' ; import { GeographyAuthorApiVersionMiddleware } from './middleware' ; import { GeographyAuthorApiRequest , GeographyAuthorApiAccessTokenScopes , GetGeographyMetadataResponse , DeleteGeographyResponse , PostGeographyResponse , PutGeographyResponse , PutGeographyMetadataResponse , GetGeographyMetadatumResponse } from './types' ; const checkGeographyAuthorApiAccess = ( validator ) => checkAccess ( validator ) ; function api ( app ) { app . use ( GeographyAuthorApiVersionMiddleware ) ; app . get ( pathsFor ( '/geographies/meta/' ) , checkGeographyAuthorApiAccess ( scopes => { return scopes . includes ( 'geographies:read:published' ) || scopes . includes ( 'geographies:read:unpublished' ) ; } ) , async ( req , res , next ) => { const { scopes } = res . locals ; const { get_published , get_unpublished } = req . query ; const params = { get_published : get_published ? get_published === 'true' : null , get_unpublished : get_unpublished ? get_unpublished === 'true' : null } ; try { if ( ! scopes . includes ( 'geographies:read:unpublished' ) && params . get_unpublished ) { throw new InsufficientPermissionsError ( 'Cannot require unpublished geo metadata without geography:read:unpublished scope' ) ; } if ( ! scopes . includes ( 'geographies:read:unpublished' ) && params . get_unpublished === null && params . get_published === null ) { params . get_published = true ; } const geography_metadata = await db . readBulkGeographyMetadata ( params ) ; return res . status ( 200 ) . send ( { version : res . locals . version , data : { geography_metadata } } ) ; } catch ( error ) { logger . error ( 'failed to read geography metadata' , error ) ; if ( error instanceof BadParamsError ) { return res . status ( 400 ) . send ( { error } ) ; } if ( error instanceof InsufficientPermissionsError ) { return res . status ( 403 ) . send ( { error } ) ; } return next ( new ServerError ( ) ) ; } } ) ; app . post ( pathsFor ( '/geographies/' ) , checkGeographyAuthorApiAccess ( scopes => scopes . includes ( 'geographies:write' ) ) , async ( req , res , next ) => { const geography = req . body ; try { const details = geographyValidationDetails ( geography ) ; if ( details ) { throw new ValidationError ( JSON . stringify ( details ) ) ; } const recorded_geography = await db . writeGeography ( geography ) ; return res . status ( 201 ) . send ( { version : res . locals . version , data : { geography : recorded_geography } } ) ; } catch ( err ) { logger . error ( 'POST /geographies failed' , err . stack ) ; if ( err . code === '23505' ) { return res . status ( 409 ) . send ( { error : `geography ${ geography . geography_id } already exists! Did you mean to PUT?` } ) ; } if ( err instanceof ValidationError ) { return res . status ( 400 ) . send ( { error : err } ) ; } return next ( new ServerError ( err ) ) ; } } ) ; app . put ( pathsFor ( '/geographies/:geography_id' ) , checkGeographyAuthorApiAccess ( scopes => scopes . includes ( 'geographies:write' ) ) , async ( req , res , next ) => { const geography = req . body ; try { const details = geographyValidationDetails ( geography ) ; if ( details ) { throw new ValidationError ( JSON . stringify ( details ) ) ; } await db . editGeography ( geography ) ; return res . status ( 201 ) . send ( { version : res . locals . version , data : { geography } } ) ; } catch ( error ) { logger . error ( 'failed to edit geography' , error . stack ) ; if ( error instanceof NotFoundError ) { return res . status ( 404 ) . send ( { error } ) ; } if ( error instanceof ValidationError ) { return res . status ( 400 ) . send ( { error } ) ; } return next ( new ServerError ( ) ) ; } } ) ; app . delete ( pathsFor ( '/geographies/:geography_id' ) , checkGeographyAuthorApiAccess ( scopes => scopes . includes ( 'geographies:write' ) ) , async ( req , res , next ) => { const { geography_id } = req . params ; try { const isPublished = await db . isGeographyPublished ( geography_id ) ; if ( isPublished ) { throw new AlreadyPublishedError ( 'Cannot delete an already published geography' ) ; } try { await db . deleteGeographyMetadata ( geography_id ) ; } catch ( err ) { logger . info ( `Unable to delete nonexistent metadata for ${ geography_id }` ) ; } await db . deleteGeography ( geography_id ) ; return res . status ( 200 ) . send ( { version : res . locals . version , data : { geography_id } } ) ; } catch ( err ) { logger . error ( 'failed to delete geography' , err . stack ) ; if ( err instanceof NotFoundError ) { return res . status ( 404 ) . send ( { error : err } ) ; } if ( err instanceof AlreadyPublishedError ) { return res . status ( 405 ) . send ( { error : err } ) ; } return next ( new ServerError ( ) ) ; } } ) ; app . get ( pathsFor ( '/geographies/:geography_id/meta' ) , checkGeographyAuthorApiAccess ( scopes => { return scopes . includes ( 'geographies:read:published' ) || scopes . includes ( 'geographies:read:unpublished' ) ; } ) , async ( req , res , next ) => { const { geography_id } = req . params ; try { const geography_metadata = await db . readSingleGeographyMetadata ( geography_id ) ; const geography = await db . readSingleGeography ( geography_id ) ; if ( ! geography . publish_date && ! res . locals . scopes . includes ( 'geographies:read:unpublished' ) ) { throw new InsufficientPermissionsError ( 'permission to read metadata of unpublished geographies missing' ) ; } return res . status ( 200 ) . send ( { version : res . locals . version , data : { geography_metadata } } ) ; } catch ( err ) { logger . error ( 'failed to read geography metadata' , err . stack ) ; if ( err instanceof NotFoundError ) { return res . status ( 404 ) . send ( { error : err } ) ; } if ( err instanceof InsufficientPermissionsError ) { return res . status ( 403 ) . send ( { error : err } ) ; } return next ( new ServerError ( ) ) ; } } ) ; app . put ( pathsFor ( '/geographies/:geography_id/meta' ) , checkGeographyAuthorApiAccess ( scopes => scopes . includes ( 'geographies:write' ) ) , async ( req , res , next ) => { const geography_metadata = req . body ; try { await db . updateGeographyMetadata ( geography_metadata ) ; return res . status ( 200 ) . send ( geography_metadata ) ; } catch ( updateErr ) { if ( updateErr instanceof NotFoundError ) { try { await db . writeGeographyMetadata ( geography_metadata ) ; return res . status ( 201 ) . send ( { version : res . locals . version , data : { geography_metadata } } ) ; } catch ( writeErr ) { logger . error ( 'failed to write geography metadata' , writeErr . stack ) ; if ( writeErr instanceof DependencyMissingError ) { return res . status ( 404 ) . send ( { error : writeErr } ) ; } return next ( new ServerError ( ) ) ; } } else { return next ( new ServerError ( updateErr ) ) ; } } } ) ; app . put ( pathsFor ( '/geographies/:geography_id/publish' ) , checkGeographyAuthorApiAccess ( scopes => scopes . includes ( 'geographies:publish' ) ) , async ( req , res , next ) => { const { geography_id } = req . params ; try { await db . publishGeography ( { geography_id } ) ; const published_geo = await db . readSingleGeography ( geography_id ) ; return res . status ( 200 ) . send ( { version : res . locals . version , data : { geography : published_geo } } ) ; } catch ( updateErr ) { if ( updateErr instanceof NotFoundError ) { return res . status ( 404 ) . send ( { error : `unable to find geography of ${ geography_id }` } ) ; } return next ( new ServerError ( updateErr ) ) ; } } ) ; app . use ( async ( error , req , res ) => { await logger . error ( req . method , req . originalUrl , error ) ; return res . status ( 500 ) . send ( { error } ) ; } ) ; return app ; } export { api } ;