Skip to content

jeopard/haskell-checking-account

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

haskell-checking-account

This repo contains a RESTful service written in Haskell using the scotty web framework that exposes some basic checking bank account functionality.

Intructions to set up and run

Stack was used for managing the packages, it is a fantastic tool that just works. You can find instructions on how to install here.

After installing Stack, clone this repo and cd into the project. In order to build the project, run

# download the compiler if needed
stack setup 

# build the project
stack build 

That's it! And then when you want to run the service at a specific port:

PORT=3000 stack exec haskell-checking-account-exe

If you want to launch a REPL:

stack ghci

You can run the tests with:

stack test

Project structure

  • app/Main.hs - the entry point for our app. It just calls Lib.main
  • haskell-checking-account.cabal - cabal file automatically generated by Stack
  • package.yaml - the file where we add all the dependencies and project metadata
  • stack.yaml - Stack configuration
  • src - contains all the code used by this service
    • Controllers/ - the service's controllers and routes
    • Core.hs - some shared code between the controllers and the server to glue everything together. It defines how the environment (memory database) is loaded to the server and defines helper functions to access the environment from the routes.
    • Models/ - the models that our app uses (e.g. Account, Operation)
    • Persistence/ - defines the (memory) Database's initial structure and helper functions to extract data from the Database. If in the future we need to replace it with some other persistence method (SQL server?), this is the directory that we'll need to edit.
    • Serializers/ - contains the code that transforms our models into JSON. Alternatively, we could have the models implementing the toJSON methods, but that's not a good practice, as one model can be serialized in many different ways, depending on what fields we want to whitelist each time.
  • test - our specs, testing all the source code. It mainly contains unit tests, but tests in test/Controllers/ could be considered integration tests, as they test the whole functionality end to end.

Database

A TVar was used for storing our database. It provides atomic concurrency-safe transactions. It is bootstrapped during the server startup, and helper functions in src/Core.hs allow it to be accessed in a transactional way.

Endpoints

POST /accounts

Creates an account. It requires an owner String in the body. The account ID returned is needed for all the following requests.

It fails with 400 if there is no owner passed.

curl -v -X POST http://localhost:3000/accounts -d '{"owner": "kostis"}'

# POST /accounts
# {
#   "owner": "kostis"
# }
#
# 200 OK
# {
#   "status": "success",
#   "data": {
#     "owner": "kostis",
#     "id": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#     "type": "Account"
#   }
# }

GET /accounts

Gets an account. Nothing too exciting. It returns 404 if the account does not exist.

curl -v http://localhost:3000/accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b

# GET /accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b
#
# 200 OK
# {
#   "status": "success",
#   "data": {
#     "owner": "kostis",
#     "id": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#     "type": "Account"
#   }
# }

POST /operations

It creates an operation for an account. The operation type can be either credit or debit. amount always appears as positive. It also requires an accountId, an ISO8601 date, and a decription.

They service supports creating operations in any order, as they are stored in a Set in the background.

If one of these fields is missing, accountId does not correspond to a created account, amount is negative, operationType is not either "credit" or "debit", it fails with 400.

curl -v -X POST http://localhost:3000/operations -d '{"accountId": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b", "operationType": "debit", "amount": 1000, "date": "2019-07-21", "description": "airline tickets to New York"}'

# POST /operations
# {
#   "accountId": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#   "operationType": "debit",
#   "amount": 1000,
#   "date": "2019-07-21",
#   "description": "airline tickets to New York"
# }
#
# 200 OK
# {
#   "status": "success",
#   "data": {
#     "amount": 1000,
#     "date": "2019-07-21",
#     "accountId": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#     "id": "4f2f24bd-5ca5-43b1-8249-f84f6c1bde97",
#     "operationType": "debit",
#     "type": "Operation",
#     "description": "airline tickets to New York"
#   }
# }

GET /accounts/:id/balance

Retrieve the account's balance. Fails with 404 if there's no account with such ID.

curl -v http://localhost:3000/accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b/balance

# GET /accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b/balance
#
# 200 OK
# {
#   "status": "success",
#   "data": -700.0
# }

GET /accounts/:id/statement

Get an accounts bank statement. fromDate and toDate query params are used to pass ISO8601 date strings that define the start and end (included) of the statement.

If the query params are missing, they're not ISO8601 strings, or fromDate is greater than toDate, the request fails with 400. If there is no account with such ID, it fails with 404.

curl -v "http://localhost:3000/accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b/statement?fromDate=2018-01-01&toDate=2020-01-01"

# GET /accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b/statement?fromDate=2018-01-01&toDate=2020-01-01
#
# 200 OK
# {
#   "status": "success",
#   "data": {
#     "fromDate": "2018-01-01",
#     "toDate": "2020-01-01",
#     "statementDates": [
#       {
#         "date": "2019-07-18",
#         "endOfDayBalance": 100,
#         "type": "StatementDate",
#         "operations": [
#           {
#             "amount": 100,
#             "date": "2019-07-18",
#             "accountId": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#             "id": "c8a18269-a212-4b22-8ab6-94e35b13da6c",
#             "operationType": "credit",
#             "type": "Operation",
#             "description": "gift from parents"
#           }
#         ]
#       },
#       {
#         "date": "2019-07-21",
#         "endOfDayBalance": -700.0,
#         "type": "StatementDate",
#         "operations": [
#           {
#             "amount": 1000,
#             "date": "2019-07-21",
#             "accountId": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#             "id": "4f2f24bd-5ca5-43b1-8249-f84f6c1bde97",
#             "operationType": "debit",
#             "type": "Operation",
#             "description": "airline tickets to New York"
#           },
#           {
#             "amount": 200,
#             "date": "2019-07-21",
#             "accountId": "dfcf2ebe-4923-4b49-ac12-7de0734daa4b",
#             "id": "df08838f-d6a6-46f7-9780-032e4f68ac36",
#             "operationType": "credit",
#             "type": "Operation",
#             "description": "some deposit"
#           }
#         ]
#       }
#     ],
#     "type": "Statement"
#   }
# }

GET /accounts/:id/debtPeriods

Returns the all-time periods of debt for the account. Again, it fails with 404 if there there's no account with such ID.

curl -v http://localhost:3000/accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b/debtPeriods

# GET /accounts/dfcf2ebe-4923-4b49-ac12-7de0734daa4b/debtPeriods
#
# 200 OK
# {
#   "status": "success",
#   "data": [
#     {
#       "fromDate": "2019-07-21",
#       "toDate": null,
#       "principal": 700.0,
#       "type": "DebtPeriod"
#     }
#   ]
# }

Response envelope

Successful responses have their status attribute set to "success", and the requested resource nested inside data. Most resources have a type declaring what this returned resource is, e.g. "Account" or "DebtPeriod".

Error responses have their status attribute set to "error", and a message attribute explaining what went wrong.

The API was developed in this way because if we wish at some point to add metadata or other data to the response irrelevant to the resource, it will be trivial.

About

Basic checking account functionality with a REST API, written in Haskell using the scotty web framework

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published