Skip to content

Commit

Permalink
feat(DTFS2-7052): new API modules and endpoint /api/v1/geospatial/add…
Browse files Browse the repository at this point in the history
…resses/postcode?postcode=W1A1AA
  • Loading branch information
avaitonis committed Apr 10, 2024
1 parent dee99b3 commit 2cdd0de
Show file tree
Hide file tree
Showing 16 changed files with 513 additions and 2 deletions.
14 changes: 13 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,19 @@
"ukef",
"venv",
"VNET",
"CICD"
"CICD",
"DPA",
"UPRN",
"UDPRN",
"BLPU",
"TOID",
"EPSG",
"WOGAN",
"osgb",
"HJLNP",
"Zabd",
"hjlnp",
"BLPUs"
],
"dictionaries": [
"en-gb",
Expand Down
6 changes: 6 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ APIM_INFORMATICA_USERNAME=
APIM_INFORMATICA_PASSWORD=
APIM_INFORMATICA_MAX_REDIRECTS=
APIM_INFORMATICA_TIMEOUT=

# ORDANANCE SURVEY
ORDNANCE_SURVEY_URL=
ORDNANCE_SURVEY_KEY=
ORDNANCE_SURVEY_MAX_REDIRECTS=
ORDNANCE_SURVEY_TIMEOUT=
3 changes: 2 additions & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import AppConfig from './app.config';
import DatabaseConfig from './database.config';
import DocConfig from './doc.config';
import InformaticaConfig from './informatica.config';
import OrdnanceSurveyConfig from './ordnance-survey.config';

export default [AppConfig, DocConfig, DatabaseConfig, InformaticaConfig];
export default [AppConfig, DocConfig, DatabaseConfig, InformaticaConfig, OrdnanceSurveyConfig];
39 changes: 39 additions & 0 deletions src/config/ordnance-survey.config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { withEnvironmentVariableParsingUnitTests } from '@ukef-test/common-tests/environment-variable-parsing-unit-tests';

import ordnanceSurveyConfig, { OrdnanceSurveyConfig } from './ordnance-survey.config';

describe('ordnanceSurveyConfig', () => {
const configDirectlyFromEnvironmentVariables: { configPropertyName: keyof OrdnanceSurveyConfig; environmentVariableName: string }[] = [
{
configPropertyName: 'baseUrl',
environmentVariableName: 'ORDNANCE_SURVEY_URL',
},
{
configPropertyName: 'key',
environmentVariableName: 'ORDNANCE_SURVEY_KEY',
},
];

const configParsedAsIntFromEnvironmentVariablesWithDefault: {
configPropertyName: keyof OrdnanceSurveyConfig;
environmentVariableName: string;
defaultConfigValue: number;
}[] = [
{
configPropertyName: 'maxRedirects',
environmentVariableName: 'ORDNANCE_SURVEY_MAX_REDIRECTS',
defaultConfigValue: 5,
},
{
configPropertyName: 'timeout',
environmentVariableName: 'ORDNANCE_SURVEY_TIMEOUT',
defaultConfigValue: 30000,
},
];

withEnvironmentVariableParsingUnitTests({
configDirectlyFromEnvironmentVariables,
configParsedAsIntFromEnvironmentVariablesWithDefault,
getConfig: () => ordnanceSurveyConfig(),
});
});
21 changes: 21 additions & 0 deletions src/config/ordnance-survey.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { registerAs } from '@nestjs/config';
import { getIntConfig } from '@ukef/helpers/get-int-config';

export const KEY = 'ordnanceSurvey';

export interface OrdnanceSurveyConfig {
baseUrl: string;
key: string;
maxRedirects: number;
timeout: number;
}

export default registerAs(
KEY,
(): OrdnanceSurveyConfig => ({
baseUrl: process.env.ORDNANCE_SURVEY_URL,
key: process.env.ORDNANCE_SURVEY_KEY,
maxRedirects: getIntConfig(process.env.ORDNANCE_SURVEY_MAX_REDIRECTS, 5),
timeout: getIntConfig(process.env.ORDNANCE_SURVEY_TIMEOUT, 30000),
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
export type GetAddressResponse = {
header: {
uri: string,
query: string,
offset: number,
totalresults: number,
format: string,
dataset: string,
lr: string,
maxresults: number,
epoch: string,
lastupdate: string,
output_srs: string
},
results?: GetAddressResponseItem[],
};

interface GetAddressResponseItem {
DPA: {
UPRN: string,
UDPRN: string,
ADDRESS: string,
BUILDING_NAME?: string,
BUILDING_NUMBER?: string,
ORGANISATION_NAME?: string;
DEPENDENT_LOCALITY?: string;
THOROUGHFARE_NAME: string,
POST_TOWN: string,
POSTCODE: string,
RPC: string,
X_COORDINATE: number,
Y_COORDINATE: number,
STATUS: string,
LOGICAL_STATUS_CODE: string,
CLASSIFICATION_CODE: string,
CLASSIFICATION_CODE_DESCRIPTION: string,
LOCAL_CUSTODIAN_CODE: number,
LOCAL_CUSTODIAN_CODE_DESCRIPTION: string,
COUNTRY_CODE: string,
COUNTRY_CODE_DESCRIPTION: string,
POSTAL_ADDRESS_CODE: string,
POSTAL_ADDRESS_CODE_DESCRIPTION: string,
BLPU_STATE_CODE: string,
BLPU_STATE_CODE_DESCRIPTION: string,
TOPOGRAPHY_LAYER_TOID: string,
LAST_UPDATE_DATE: string,
ENTRY_DATE: string,
BLPU_STATE_DATE: string,
LANGUAGE: string,
MATCH: number,
MATCH_DESCRIPTION: string,
DELIVERY_POINT_SUFFIX: string
}
}

// interface GetAddressResponseAddress {
// UPRN: string,
// UDPRN: string,
// ADDRESS: string,
// BUILDING_NAME?: string,
// BUILDING_NUMBER?: string,
// ORGANISATION_NAME?: string;
// DEPENDENT_LOCALITY?: string;
// THOROUGHFARE_NAME: string,
// POST_TOWN: string,
// POSTCODE: string,
// RPC: string,
// X_COORDINATE: number,
// Y_COORDINATE: number,
// STATUS: string,
// LOGICAL_STATUS_CODE: string,
// CLASSIFICATION_CODE: string,
// CLASSIFICATION_CODE_DESCRIPTION: string,
// LOCAL_CUSTODIAN_CODE: number,
// LOCAL_CUSTODIAN_CODE_DESCRIPTION: string,
// COUNTRY_CODE: string,
// COUNTRY_CODE_DESCRIPTION: string,
// POSTAL_ADDRESS_CODE: string,
// POSTAL_ADDRESS_CODE_DESCRIPTION: string,
// BLPU_STATE_CODE: string,
// BLPU_STATE_CODE_DESCRIPTION: string,
// TOPOGRAPHY_LAYER_TOID: string,
// LAST_UPDATE_DATE: string,
// ENTRY_DATE: string,
// BLPU_STATE_DATE: string,
// LANGUAGE: string,
// MATCH: number,
// MATCH_DESCRIPTION: string,
// DELIVERY_POINT_SUFFIX: string
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class GetSearchPostcodeOrdnanceSurveyQueryDto {
public postcode: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"header": {
"uri": "https://api.os.uk/search/places/v1/postcode?postcode=CV1%2011M",
"query": "postcode=CV1 11M",
"offset": 0,
"totalresults": 0,
"format": "JSON",
"dataset": "DPA",
"lr": "EN,CY",
"maxresults": 100,
"epoch": "109",
"lastupdate": "2024-04-05",
"output_srs": "EPSG:27700"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"header": {
"uri": "https://api.os.uk/search/places/v1/postcode?postcode=W1A%201AA",
"query": "postcode=W1A 1AA",
"offset": 0,
"totalresults": 1,
"format": "JSON",
"dataset": "DPA",
"lr": "EN,CY",
"maxresults": 100,
"epoch": "109",
"lastupdate": "2024-04-09",
"output_srs": "EPSG:27700"
},
"results": [
{
"DPA": {
"UPRN": "10092008000",
"UDPRN": "25733000",
"ADDRESS": "BRITISH BROADCASTING CORPORATION, WOGAN HOUSE, PORTLAND PLACE, LONDON, W1A 1AA",
"ORGANISATION_NAME": "BRITISH BROADCASTING CORPORATION",
"BUILDING_NAME": "WOGAN HOUSE",
"THOROUGHFARE_NAME": "PORTLAND PLACE",
"POST_TOWN": "LONDON",
"POSTCODE": "W1A 1AA",
"RPC": "2",
"X_COORDINATE": 528960.0,
"Y_COORDINATE": 181680.0,
"STATUS": "APPROVED",
"LOGICAL_STATUS_CODE": "1",
"CLASSIFICATION_CODE": "OR00",
"CLASSIFICATION_CODE_DESCRIPTION": "Additional Mail / Packet Addressee",
"LOCAL_CUSTODIAN_CODE": 7600,
"LOCAL_CUSTODIAN_CODE_DESCRIPTION": "ORDNANCE SURVEY",
"COUNTRY_CODE": "E",
"COUNTRY_CODE_DESCRIPTION": "This record is within England",
"POSTAL_ADDRESS_CODE": "D",
"POSTAL_ADDRESS_CODE_DESCRIPTION": "A record which is linked to PAF",
"BLPU_STATE_CODE": "2",
"BLPU_STATE_CODE_DESCRIPTION": "In use",
"TOPOGRAPHY_LAYER_TOID": "osgb1000005200000",
"PARENT_UPRN": "100023600000",
"LAST_UPDATE_DATE": "29/01/2023",
"ENTRY_DATE": "19/01/2012",
"BLPU_STATE_DATE": "01/01/2020",
"LANGUAGE": "EN",
"MATCH": 1.0,
"MATCH_DESCRIPTION": "EXACT",
"DELIVERY_POINT_SUFFIX": "1A"
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';

import { OrdnanceSurveyException } from './ordnance-survey.exception';

describe('OrdnanceSurveyException', () => {
const valueGenerator = new RandomValueGenerator();
const message = valueGenerator.string();

it('exposes the message it was created with', () => {
const exception = new OrdnanceSurveyException(message);

expect(exception.message).toBe(message);
});

it('exposes the name of the exception', () => {
const exception = new OrdnanceSurveyException(message);

expect(exception.name).toBe('OrdnanceSurveyException');
});

it('exposes the inner error it was created with', () => {
const innerError = new Error();

const exception = new OrdnanceSurveyException(message, innerError);

expect(exception.innerError).toBe(innerError);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class OrdnanceSurveyException extends Error {
constructor(
message: string,
public readonly innerError?: Error,
) {
super(message);
this.name = this.constructor.name;
}
}
13 changes: 13 additions & 0 deletions src/helper_modules/ordnance-survey/known-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NotFoundException } from '@nestjs/common';
import { AxiosError } from 'axios';

export type KnownErrors = KnownError[];

type KnownError = { caseInsensitiveSubstringToFind: string; throwError: (error: AxiosError) => never };

export const getCustomersNotFoundKnownOrdnanceSurveyError = (): KnownError => ({
caseInsensitiveSubstringToFind: 'Company registration not found',
throwError: (error) => {
throw new NotFoundException('Customer not found.', error);
},
});
26 changes: 26 additions & 0 deletions src/helper_modules/ordnance-survey/ordnance-survey.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { OrdnanceSurveyConfig, KEY as ORDNANCE_SURVEY_CONFIG_KEY } from '@ukef/config/ordnance-survey.config';
import { HttpModule } from '@ukef/modules/http/http.module';

import { OrdnanceSurveyService } from './ordnance-survey.service';

@Module({
imports: [
HttpModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const { baseUrl, maxRedirects, timeout } = configService.get<OrdnanceSurveyConfig>(ORDNANCE_SURVEY_CONFIG_KEY);
return {
baseURL: baseUrl,
maxRedirects,
timeout,
};
},
}),
],
providers: [OrdnanceSurveyService],
exports: [OrdnanceSurveyService],
})
export class OrdnanceSurveyModule {}
Loading

0 comments on commit 2cdd0de

Please sign in to comment.