Making parking smoother than the jazz king, Yardbird, himself! This app gave me an opportunity to flex and sharpen my Go/REST api/AWS Dynamo DB/Docker skills!
charlie-parker
βββ docker-compose.local.yml
βββ Dockerfile
βββ go.mod
βββ go.sum
βββ main.go -- blank main file to allow for testing
βββ README.md
βββ tools.sh -- command line tools for running the app
βββ cmd
| βββ seeder
| | βββ main.go -- app entry for seeding local dynamo
| βββ server
| βββ main.go -- app entry for running the api server
βββ internal
| βββ config
| | βββ config.go -- init() for app-wide configuration
| βββ helpers
| | βββ rates.go -- helper funcs for routes in \routes\rates.go
| | βββ routemetrics.go -- helper funcs for route metrics and routes in \routes\routemetrics.go
| | βββ util_test.go -- tests for util.go
| | βββ util.go -- general helper functions for data manipulation
| | βββ validate_test.go -- tests for validate.go
| | βββ validate.go -- validation functions for route inputs
| βββ routes
| | βββ rates.go -- rate-related route handlers
| | βββ routemetrics.go -- metrics-related route handlers
| βββ seeder
| | βββ seed_data.go -- defines a list of CreateRateInput used to seed
| | βββ seeder.go -- exports Run() that runs the seeder
| βββ server
| βββ server.go -- exports Start() that starts the server
βββ pkg \ types
| βββ rates.go -- defines the rate struct and input/output types to rate-related routes
| βββ routemetrics.go -- defines the route metrics struct and input/output types to metrics-related routes
| βββ utiltypes.go -- defines the BaseOutput type that contains Ok and Error fields
βββ utils
| βββ utilroutes.go -- HeartbeatRoute() to check app alive-ness
βββ vendor -- vendored dependencies
This app allows for the storage and retrieval of parking rates that have a comma separated list of days for which they cover, a time span in the format "HHMM-HHMM", a time zone, and a price. Rates must not define a time range that is already covered by a rate. For example, a rate that covers 9am-2pm on Fridays in the timezone America/Chicago may not be created if a rate that covers 12pm-1pm on Fridays in the timezone America/Chicago already exists.
A set of start and end times strings may be sent to the app's server, if there is a rate that covers that time range, its price will be returned to the client. Start and end must:
- parse in ISO-8601 format
- be in the same year.
- be on the same day.
- not be the same time.
- be one earlier hour and one later hour.
- be in the same timezone.
In order for a set of start and end times to match to an existing rate, a rate must exist:
- that covers the day on which start and end fall.
- that matches the start and end's timezone.
- for which start is greater than or equal to its start time
- for which start is less than its end time
- for which end is greater than its start time
- for which end is less than or equal to its end time
Beyond this business functionality, average response time, API endpoint hits, number of successful exchanges, and number of failed exchanges are metrics measured for each route defined for the server.
Currently, this app is only set up for a local environment. You will need Docker and Go installed in order to run. (You can either follow the instructions below or alternatively run the file tools.sh with the argument local
to run a script to build and test this app entirely.)
In your command prompt navigate to where you have cloned this repo:
Run Dynamo on port 8000:
docker run -d -p 8000:8000 -v ~/:/var/lib/dynamodb -it --rm --name cp-dynamo instructure/dynamo-local-admin
Build the seeder:
docker build -t charlie-parker-seeder:latest --build-arg app=seeder .
Build the server:
docker build -t charlie-parker-server:latest --build-arg app=server .
Run the seeder:
docker run -e AWS_ACCESS_KEY_ID=key -e AWS_SECRET_ACCESS_KEY=secret -e AWS_REGION=us-east-1 --name cp-seeder charlie-parker-seeder:latest
Run the server:
docker run -e AWS_ACCESS_KEY_ID=key -e AWS_SECRET_ACCESS_KEY=secret -e AWS_REGION=us-east-1 -p 8554:8554 --name cp-server charlie-parker-server:latest
(The environment variables related to AWS are needed to connect to the local dynamo tables)
Tests are defined for two files: internal\helpers\utils.go and internal\helpers\validate.go in utils_test.go and validate_test.go respectively. These two files contain most of the business logic and do not need a DB connection nor an HTTP request to test. These are the tests run when Docker is building the app; they may also be run individually.
In order to test the routes defined for the server, build the app and use the following curl commands.
This route gets all rates that are in the rates table. If you did not run the seeder or have deleted all of the rates, none will be returned.
Mac/Linux/Windows:
curl -X GET http://localhost:8554/api/v1/rates
This route creates a rate based on the following required input:
Days
any substring of"sun,mon,tues,wed,thurs,fri,sat"
Times
a string range of 24-hour time hours and minutes in the format of"HHMM-HHMM"
TZ
a string timezone (i.e."America/Chicago"
)Price
an integer (represents number of cents charged per hour)
Mac/Linux:
curl -X POST -H "Content-Type: application/json" -d '{"Days": "fri", "Times": "1600-1800", "TZ": "America/Chicago", "Price": 1800}' http://localhost:8554/api/v1/rates/create
Windows:
curl -X POST -H "Content-Type: application/json" -d "{\"Days\": \"fri\", \"Times\": \"1600-1800\", \"TZ\": \"America/Chicago\", \"Price\": 1800}" http://localhost:8554/api/v1/rates/create
This route is a useful route for batch creating a large set of new rates. It overwrites all existing rates in the DB. This is obviously not a useful route if this were a real world app where we'd probably want to keep old ratese around, but for now, since this is all local and the containers will be torn down anyway, this is a useful route incase the user would like to test creating a whole bunch of different rates. The required input is a list of inputs of the same fields used in the create rate route:
Mac/Linux:
curl -X POST -H "Content-Type: application/json" -d '{"Rates": [{"Days": "fri", "Times": "1600-1800", "TZ": "America/Chicago", "Price": 1800}, {"Days": "fri", "Times": "0900-1200", "TZ": "America/Chicago", "Price": 500}]}' http://localhost:8554/api/v1/rates/update/all
Windows:
curl -X POST -H "Content-Type: application/json" -d "{\"Rates\": [{\"Days\": \"fri\", \"Times\": \"1600-1800\", \"TZ\": \"America/Chicago\", \"Price\": 1800}, {\"Days\": \"fri\", \"Times\": \"0900-1200\", \"TZ\": \"America/Chicago\", \"Price\": 500}]}" http://localhost:8554/api/v1/rates/update/all
This route tries to find a rate based on the following required input:
Start
a string in the format"2017-01-06T17:00:00-06:00"
End
a string in the format"2017-01-06T018:00:00-06:00"
(These will return a price only if you've used the create or overwrite examples above)
Mac/Linux:
curl -X POST -H "Content-Type: application/json" -d '{"Start": "2017-01-06T17:00:00-06:00", "End": "2017-01-06T18:00:00-06:00"}' http://localhost:8554/api/v1/park
Windows:
curl -X POST -H "Content-Type: application/json" -d "{\"Start\": \"2017-01-06T17:00:00-06:00\", \"End\": \"2017-01-06T18:00:00-06:00\"}" http://localhost:8554/api/v1/park
This route gets the route metrics for all of the routes defined by this app. If hit right after a fresh build of the app, this route will return no metrics. Hit a few other routes, or this one a couple more times, then call this one to see the metrics come in!
Mac/Linux/Windows:
curl -X GET http://localhost:8554/api/health/routes