Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/dof-dss/edd-pointerapi

Simple unambitious postcode lookup web Api for Northern Ireland Addresses
https://github.com/dof-dss/edd-pointerapi

address-lookup addresses northern-ireland postcode-lookup-service

Last synced: 28 days ago
JSON representation

Simple unambitious postcode lookup web Api for Northern Ireland Addresses

Awesome Lists containing this project

README

        

# EDD-PointerApi

| Builds | Branch | Status
| ------------- | ----- |--------
| Circle CI | main | [![CircleCI](https://circleci.com/gh/dof-dss/EDD-PointerApi/tree/main.svg?style=svg&circle-token=ee183d3b060895f03ad7976452ede418db21e489)](https://circleci.com/gh/dof-dss/EDD-PointerApi/tree/main)
| SonarCloud | main | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dof-dss_EDD-PointerApi&metric=alert_status)](https://sonarcloud.io/dashboard?id=dof-dss_EDD-PointerApi)

## Description
This is a simple unambitious postcode address lookup Api for Northern Ireland addresses.

e.g. from a consuming application the user adds a postcode

![lookup](https://user-images.githubusercontent.com/66303816/121494861-05f19b80-c9d1-11eb-8656-414decdb57ee.png)

And the api displays the results

![lookupList](https://user-images.githubusercontent.com/66303816/121495064-333e4980-c9d1-11eb-9a69-14120ea3de8e.png)

## Contents of this file

- [Contributing](#contributing)
- [Licensing](#licensing)
- [Project Documentation](#project-documentation)
- [Why did we build this project](#why-did-we-build-this-project)
- [What problem was it solving](#what-problem-was-it-solving)
- [How did we do it](#how-did-we-do-it)
- [Future Plans](#future-plans)
- [Deployment Guide](#deployment-guide)

## Contributing

Contributions are welcomed! Read the [Contributing Guide](./docs/contributing/Index.md) for more information.

## Licensing

Unless stated otherwise, the codebase is released under the MIT License. This covers both the codebase and any sample code in the documentation. The documentation is © Crown copyright and available under the terms of the Open Government 3.0 licence.

## Project Documentation

### Why did we build this project?

We built this so applications can allow users to enter their postcode and recieve a list of addresses, in order to select their address. We built this api so the data and functionality can be shared by many applications.

### What problem was it solving?

This solves having to create a pointer table in every single application and adding the same code over and over again. There are 3 main endpoints:

- Search by postcode
- Search by postcode and premises number
- Search by x and y co-ordinates which is handy for plotting a point on a map for example

### How did we do it?

This is a dotnet core application which uses Mysql to store the pointer data, Entity Framework for data access and JWT to authenticate applications to allow them to use the api.
We have hosted this in the Gov UK PaaS Cloud foundry platform using Circle CI to deploy.

### Future plans

We may introduce a more advanced search if needed.

### Deployment guide

To run the databases you need mysql installed. Then run the below commands to set up the database:

- update-database

Restore the nuget package. Then to build run "dotnet build" in command line then dotnet run to run the site.

### Dataset

You can obtain the dataset which is around one million addresses from OSNI / LPS in csv format and manually input this into the database.
Once you get the dataset you will need to import the dataset into MySql. I did this using the below mysql script (you may need to alter the date fields):

```

LOAD DATA INFILE 'C:/ProgramData/MySQL/MySQL Server 8.0/Uploads/ALLNI_20201222_F.csv'
INTO TABLE pointer.pointer
CHARACTER SET cp1250
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\r\n'

IGNORE 1 ROWS;

```

You will also want to create an index to make it super fast:

```

CREATE INDEX PostcodeIndex
ON pointer.pointer (Postcode(8));

```

### Usage from consuming application

The consuming application will need a secret key and the api base address which they can obtain from DoF EDD.
To acutally use the api from your application you will need a view (I did this as a partial view), a pointer model, an address model / interface, a javascript file to interact with the view and a controller to execute the search.
Below are examples of how I did it:

Javascript for view

```
$('#SearchPostCode').on('keyup keypress', function (e)
{
var keyCode = e.keyCode || e.which;
if (keyCode === 13)
{
getAddresses();
e.preventDefault();
return false;
}
});

function getAddresses()
{
let postCode = $("#SearchPostCode").val();
$("#addressError").hide();

if (postCode != "")
{
$("#loadSpinner").show();

$.get('/Pointer/GetAddresses/', { postCode: postCode }, function (data) {
$("#SearchAddress").empty();
$("#SearchAddress").append($("Select Address"));

$.each(data, function ()
{
$(".govuk-error-summary").hide();
$("#loadSpinner").hide();
$("#SearchAddressList").show();
$("#addressError").hide();
$("#SearchAddress").append($("").val(this["building_Number"]).html(this["building_Number"] + ' ' + this["primary_Thorfare"] + ',' + this["town"] + ',' + this["postcode"]));
});
}).fail(function ()
{
$(".govuk-error-summary").show();

if ($(".error-items").length === 0)
{
$(".govuk-error-summary__list").append("

  • Not a real postcode. Address could not be found.
  • ");
    }

    $("#PostCodeSearchComponent").addClass("govuk-form-group--error");
    $("#SearchPostCode").addClass("govuk-input--error");
    $("#SearchPostCode").val("Not a postcode")
    $("#addressError").show();
    $("#loadSpinner").hide();
    $("#SearchAddressList").hide();
    });
    }
    }

    function fillAddressTextBoxes() {
    let myText = $("#SearchAddress :selected").text();

    if (myText != "Select Address")
    {
    let addressArray = myText.split(',');

    $("#Address1").val("");
    $("#Address2").val("");
    $("#Address3").val("");
    $("#TownCity").val("");
    $("#PostCode").val("");

    $("#Address1").val(addressArray[0]);
    $("#TownCity").val(addressArray[1]);
    $("#PostCode").val(addressArray[2]);
    }
    }
    ```

    Controller to manage search

    ```
    using JWT;
    using JWT.Algorithms;
    using JWT.Serializers;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using Newtonsoft.Json;
    using probate.Config;
    using probate.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;

    namespace probate.Controllers
    {
    public class PointerController : Controller
    {
    private readonly IHttpClientFactory _pointerClient;
    private readonly IOptions _pointerConfig;

    public PointerController(IHttpClientFactory pointerClient, IOptions pointerConfig)
    {
    _pointerClient = pointerClient;
    _pointerConfig = pointerConfig;
    }

    [HttpGet]
    public async Task GetAddressesAsync(string postCode)
    {
    var client = _pointerClient.CreateClient("PointerClient");

    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + CreateJwtToken());

    var result = await client.GetAsync("PostCodeSearch/" + postCode);

    List pointerAddresses = new List();

    if (result.IsSuccessStatusCode)
    {
    using (HttpContent content = result.Content)
    {
    var resp = content.ReadAsStringAsync();
    pointerAddresses = JsonConvert.DeserializeObject>(resp.Result).ToList();
    }
    }

    return Json(pointerAddresses);
    }

    private string CreateJwtToken()
    {
    var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    var iat = Math.Round((DateTime.UtcNow - unixEpoch).TotalSeconds);

    var payload = new Dictionary
    {
    { "iat", iat },
    { "kid", _pointerConfig.Value.kid }
    };

    IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
    IJsonSerializer serializer = new JsonNetSerializer();
    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
    IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

    var jwtToken = encoder.Encode(payload, _pointerConfig.Value.secret);
    return jwtToken;
    }
    }
    }
    ```

    Pointer model

    ```
    public class Pointer
    {
    public string Organisation_Name { get; set; }
    public string Sub_Building_Name { get; set; }
    public string Building_Name { get; set; }
    public string Building_Number { get; set; }
    public string Primary_Thorfare { get; set; }
    public string Alt_Thorfare_Name1 { get; set; }
    public string Secondary_Thorfare { get; set; }
    public string Locality { get; set; }
    public string Townland { get; set; }
    public string Town { get; set; }
    public string County { get; set; }
    public string Postcode { get; set; }
    public string BLPU { get; set; }
    public int Unique_Building_ID { get; set; }
    public int UPRN { get; set; }
    public int USRN { get; set; }
    public string Local_Council { get; set; }
    public int X_COR { get; set; }
    public int Y_COR { get; set; }
    public string Temp_Coords { get; set; }
    public string Building_Status { get; set; }
    public string Address_Status { get; set; }
    public string Classification { get; set; }
    public string Creation_Date { get; set; }
    public string Commencement_Date { get; set; }
    public string Archived_Date { get; set; }
    public string Action { get; set; }
    public string UDPRN { get; set; }
    public string Posttown { get; set; }
    }

    ```

    This is my partial view which is reused in our applications

    ```
    @model probate.Models.IAddress



    To find your address, enter a valid Northern Ireland postcode and select find address.



    Postcode


    Error: Enter a real postcode


    Find address



    Loading, please wait





    Select an address





    If you cannot find your address, enter your details below.







    Address Line 2 (optional)



    Address Line 3 (optional)



    Town or city




    Postcode




    Country (optional)



    ```

    IAddress interface used to capture the address

    ```
    public interface IAddress
    {
    public string SearchAddress { get; set; }
    public string SearchPostCode { get; set; }

    [DisplayName("Address line 1")]
    [Required(ErrorMessage = "Enter address line 1")]
    [StringLength(35, ErrorMessage = "{0} must be a string with a maximum length of {1}")]
    public string Address1 { get; set; }

    [DisplayName("Address line 2")]
    [StringLength(35, ErrorMessage = "{0} must be a string with a maximum length of {1}")]
    public string Address2 { get; set; }

    [DisplayName("Address line 3")]
    [StringLength(35, ErrorMessage = "{0} must be a string with a maximum length of {1}")]
    public string Address3 { get; set; }

    [DisplayName("Town or city")]
    [StringLength(35, ErrorMessage = "{0} must be a string with a maximum length of {1}")]
    [Required(ErrorMessage = "Enter town or city")]
    public string TownCity { get; set; }

    [DisplayName("Post code")]
    [Required(ErrorMessage = "Enter post code")]
    [StringLength(8, ErrorMessage = "{0} must be a string with a maximum length of {1}")]
    public string PostCode { get; set; }

    [DisplayName("Country")]
    [StringLength(35, ErrorMessage = "{0} must be a string with a maximum length of {1}")]
    public string Country { get; set; }
    }

    ```