Skip to content

RyanSamman/FinanceAPI

Repository files navigation

Table of Contents

Environment Variables

Copy the .env.example file, and rename it to .env.

PORT="6000"
POSTGRES_PASSWORD=postgres
POSTGRES_USER=postgres
POSTGRES_DB=tamara

This configuration works as-is. However, it can be customized to fit your needs.

Installation

Before installation, make sure you have setup the environment variables correctly.

DockerHub Installation

On DockerHub, a production-ready container is available.

Docker Installation

To build the container needed, use yarn docker:build.

Afterwards, you have two options:

  • yarn docker:dev to run in Development Mode
  • yarn docker:start to run in Production Mode

Manual Installation

Alternatively, you can manually start the server with the following scripts:

  • yarn install all dependencies.
  • yarn dev start the server in development mode.
  • yarn build compile the Typescript files.
  • yarn start start the server in production mode.
  • yarn test run Unit & Integration tests.

However, these do not create a database instance.

More scripts here.

Linting

The entire project is linted with eslint. The settings.json file contains the settings needed to automatically lint on save.

To configure the linter, open the eslintrc.json file.

Upon running Github Actions, the project is linted to ensure the code is up to standard.

Scripts

A script can be run by yarn <script>

  • build Builds the Typescript files into the /dist folder.
  • build:clean Cleans the \dist folder.
  • build:watch Re-Builds upon any change to the files.
  • dev Runs the server and watches for any changes.
  • start runs the compiled server in production mode. Compile first with build!
  • lint Lints the project.
  • lint:fix Lints the project and fixes any errors.
  • test Tests the application Run docker:test first!
  • migration:create Create a new Database Migration file.
  • migration:generate Automatically Generate a new Database Migration.
  • docker:build Build the server's docker container.
  • docker:dev Runs the server's docker container in development mode, watching for any changes.
  • docker:test Creates the database for the tests.
  • docker:prod Runs the server's docker container in production mode.
  • docker:down Turns off all active containers.

Github Actions

Upon pushing to GitHub code is linted, tested, and checked if it compiles. Afterwards, a Docker image is built and pushed to DockerHub.

API Documentation

Utility Methods

Load Configuration

All Neccessary .env variables are checked and sanitized by this method.

Below are the available variables which are loaded:

interface Config {
	PORT: number
	DOCKER: boolean
	POSTGRES_DB: string
	POSTGRES_USER: string
	NODE_ENV: 'production' | 'test' | 'development'
	POSTGRES_PASSWORD: string
	DUE_AFTER: TimeValues
}

To access these variables import it like the following:

import { config } from './util'

Logger

A logger is available by importing it like the following:

import { logger } from './util'

The logger can be customized from the utility file. By default, the logger is customized as the following:

// Function that does nothing (No Operation)
const noop = (..._data: unknown[]) => { }

const logger = new Logger({
	onLog: config.NODE_ENV !== 'development' ? noop : console.log,
	onInfo: config.NODE_ENV === 'test' ? noop : (...data) => console.log(...data),
	onError: (...data) => console.error(...data),
})
  • onError will always be defined.
  • onInfo will not log in a test environment.
  • onLog will only log in a development environment.

This allows the flexibility to quickly change what is being logged for the entire server, and may be integrated with logging services in the future.

API Types - ResponseData

All routes in the API which contain a JSON response sends a variant of ResponseData. Either a data field is defined, or an errors field is defined.

API Type - data

If the data field is defined, it will be of type T, which is the generic type of ResponseData. By default, it is a string.

{
	"data": T
}

API Type - errors

If the errors field is defined, it will be an array of one three types:

  • Result<ValidationError>
  • ResponseError
  • ServerError

All elements in the errors array have these values defined:

{
	param: ...
	msg: ...
	location: ...
}
  • param Is the parameter which caused the error.
  • msg Is a description of the error.
  • location is where the error has occurred.

The errors array may return multiple error elements, such as the following for example:

{
  "errors": [
    {
      "msg": "Duplicate Name",
      "param": "name",
      "location": "body"
    },
	{
	  "msg": "Invalid ID",
	  "param": "userId",
	  "location": "params"
	}
  ]
}

For the ServerError, it is the only type which has a location of error. The other two are interchangable.

interface ResponseError {
	param: string
	msg: string | Error
	location: 'body' | 'cookies' | 'headers' | 'params' | 'query'
}

interface ServerError {
	param: string
	msg: string | Error
	location: 'error'
}

interface ResponseData<T = string> {
	data?: T
	errors?: Result<ValidationError>[] | ResponseError[] | ServerError[]
}

Middleware

All utility middleware used is contained here.

Logger Middleware

All routes are logged with a custom logger middleware. Afterwards, it is passed onto the next middleware.

  • Uses the logger's log method.

Speed Limiter

Routes are speed-limited with express-slow-down to prevent DDoS attacks.

Settings

{
	// How long a request is is stored in milliseconds. 
	// Here, it is set to 15 minutes.
	windowMs: 15 * 60 * 1000, 
	// The number of requests made before any slowdown occurs.
	// Here, it is set to 100 requests.
	delayAfter: 100,
	// The amount of time in 'ms' added after each request exceeding 'delayAfter'
	delayMs: 500,
}

handleErrors

This middleware responds with any expected errors found by express-validator's middleware.

Error Route Middeware

This middleware handles and logs any uncaught errors which occur within the API. express-async-errors directs any unhandled errors to this route.

In a production NODE_ENV, this is the JSON sent:

{ errors: [{ 'error': 'Server Error.' }]}

However, in test and development environments, the error itself is returned instead of Server Error.

{ errors: [{ 'error': err }] }
  • Returns a 500 response code.
  • Uses the logger's error method.

Status

This route is used to check if the API has been setup properly, and for health checks.

HEAD /status

  • Returns a 200 response code.

GET /status

  • Returns a 200 response code.
  • Returns a JSON response: ResponseData<string>
{
	"data": "OK"
}

404 Middleware

If no route for the request has been found, the server will run this middleware.

  • Returns a 404 response code.

  • Returns a JSON response: ResponseData { errors: [ { "location": "error",

      }
    

    ] }

User Routes - /user

Create User - POST /user/new

Requires a body:

{
	"name": string
}

Where name is a unique string between 3 and 20 characters long.

On a Valid Request:

  • Returns a 201 response code.
  • Returns a JSON response: ResponseData<User>
{
  "data": {
    "name": "Joe",
    "userId": 1,
    "createdAt": "2021-05-02T06:48:01.905Z",
    "updatedAt": "2021-05-02T06:48:01.905Z"
  }
}

Errors: Read here for Error body description.

  • 400 Bad Request
    • Invalid name, must be a string between 3 and 20 characters long.
    • Duplicate name.

Get All Users - GET /user/all

  • Always returns a 200 response code.
  • Returns a JSON response: ResponseData<User[]>
{
  "data": [
    {
      "userId": 2,
      "name": "Ryan",
      "createdAt": "2021-05-02T06:49:16.818Z",
      "updatedAt": "2021-05-02T06:49:16.818Z",
      "payments": [],
      "paymentHistory": []
    },
    {
      "userId": 1,
      "name": "Joe",
      "createdAt": "2021-05-02T06:48:01.905Z",
      "updatedAt": "2021-05-02T06:48:01.905Z",
      "payments": [],
      "paymentHistory": []
    }
  ]
}

Get User - Get /user/:userId

Requires the userId param.

On a Valid Request:

  • Returns a 200 response code.
  • Returns a JSON response: ResponseData<User>
{
  "data": {
    "name": "Joe",
    "userId": 1,
    "createdAt": "2021-05-02T06:48:01.905Z",
    "updatedAt": "2021-05-02T06:48:01.905Z"
  }
}

Errors: Read here for Error body description.

  • 400 Bad Request
    • Invalid userId, it must be a number.
  • 404 Not Found
    • The userId was not found.

Create new Payment - POST /payment/new

Requires a body:

{
 "userId": number,
 "amount": number,
 "currency": Currency,
}

List of currencies supported here

On a Valid Request:

  • Returns a 201 response code.
  • Returns a JSON response: ResponseData<string>
{
  "data": "Successfully created a new Payment"
}

Errors: Errors: Read here for Error body description.

  • 400 Bad Request
    • Invalid userId, it must be a number.
    • Invalid amount, it must be a number.
    • Invalid currency, it must be a in the list of supported currencies.
  • 404 Not Found
    • The userId was not found.

Get all of a User's Payments - GET /payment/:userId/all

Requires the parameter :userId Retrieves a list of all a user's current payments.

On a Valid Request:

  • Returns a 201 response code.
  • Returns a JSON response: ResponseData<Payment>
{
  "data": [
    {
      "paymentId": 1,
      "currency": "AUD",
      "amount": "200.0000000000",
      "paymentStatus": "underway",
      "createdAt": "2021-05-02T07:57:50.185Z",
      "updatedAt": "2021-05-02T07:57:50.185Z",
      "dueAt": "2021-05-09T07:57:50.181Z",
      "paidAt": null,
      "deleted": false,
      "sentDueReminder": false,
      "paymentHistory": [
        {
          "paymentRecordId": 1,
          "recordKind": "creation",
          "amount": "200.0000000000",
          "currency": "AUD",
          "createdAt": "2021-05-02T07:57:50.205Z"
        }
      ]
    }
  ]
}

Errors: Read here for Error body description.

  • 400 Bad Request
    • Invalid userId, it must be a number.
  • 404 Not Found
    • The userId was not found.

Pay a User's Payment - /payment/pay

Requires a body

{
    "userId": 1,
    "paymentId": 4,
    "amount": 250,
    "currency": "AUD"
}
  • Returns a 201 response code.
  • Returns a JSON response
{
  "data": {
    "message": "Paid off Payment #4.",
    "balance": 0,
    "overcharge": 50,
    "paymentStatus": "paid"
  }
}

message will be a message denoting:

  • how much has been paid if there is a remaining balance
  • If the payment is complete, it will say it has been paid off.

balance is the amount remaining from the payment

overcharge is the amount given back to the user if they have paid too much

paymentStatus is the status of the payment,

  • underway if it is still not completely paid
  • paid if it has been paid off
  • due if payment is due

Payment

Delete a payment - /payment/delete

Request body:

{
    "userId": 1,
    "paymentId": 1
}
  • Returns a 200 status
  • Returns a JSON response:
{
  "data": "This payment has been deleted."
}

Errors:

  • Returns a 404 response if payment is not found.

Transactions

Get all Transactions - /transaction/:userId/all

Requires userId parameter

  • Returns a 200 response
  • Returns JSON: ResponseData<PaymentRecord[]>

Reminders

Get All Reminders - /reminder/:userId

Requires a :userId parameter

  • returns a 200 response
  • Returns JSON: ResponseData<Payment[]>

About

A quick iteration of a payment API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages