# Packages
# README
Hack the North Backend
Submission for the Hack the North 2023 Backend Challenge.
Table of Contentx
Overview
This Go project compiles to a single binary that is capable of:
- running migration scripts on a SQLite database
- populating SQLite with mock data
- serving user data through a REST API
The main libraries used are:
- cobra: a library for handling CLI arguments and flags
- gin: a web framework for routing HTTP requests
- golang-migrate: a library to run migration scripts from a specified directory
- gorm: an ORM to interact with SQLite
Project Structure
- cmd: the main initial logic is found in the
migrate.go
,populate.go
, andserve.go
files - migration: contains migration script
- model: defines Go structs for unmarshalling JSON and for the ORM
- repository: contains SQLite query logic
- main.go: starting point
Setup and Installation
Requirements:
- Git
- Docker
Note: Go is not required for setup and this guide does NOT assume you have it installed.
Clone
$ git clone [email protected]:Aerilate/htn-backend.git
$ cd htn-backend
Basic Usage
# in one command, sets up the database and runs the server
$ docker compose up
$ curl localhost:8080/ping
pong
# now try the examples in the API section below!
API
GET /users/
Retrieves information on all users in the database.
Return
- On failure, returns 404.
- On success, returns 200 with a body of the following schema:
[
{
"name": <string>,
"company": <string>,
"email": <string>,
"phone": <string>,
"skills": [
{
"skill": <string>,
"rating": <int>
}
]
},
...
]
Example
$ curl localhost:8080/users/
[{"name":"Breanna Dillon","company":"Jackson Ltd","email":"[email protected]","phone":"+1-924-116-7963","skills":[{"skill":"OpenCV","rating":1},{"skill":"Swift","rating":4}]},{"name":"Kimberly Wilkinson","company":"Moon, Mendoza and Carter","email":"[email protected]","phone":"(186)579-0542","skills":[{"skill":"Elixir","rating":4},{"skill":"Fortran","rating":2},{"skill":"Foundation","rating":4},{"skill":"Plotly","rating":3}]},
...
GET /users/<id>
Retrieve information on the user with the given id.
Return
- On invalid id argument (i.e. not a number), returns 400.
- On other failure, returns 404.
- On success, returns 200 with a body of the following schema:
{
"name": <string>,
"company": <string>,
"email": <string>,
"phone": <string>,
"skills": [
{
"skill": <string>,
"rating": <int>
}
]
}
Example
$ curl localhost:8080/users/1
{"name":"Breanna Dillon","company":"Jackson Ltd","email":"[email protected]","phone":"+1-924-116-7963","skills":[{"skill":"OpenCV","rating":1},{"skill":"Swift","rating":4}]}
PUT /users/<id>
Update some information about a user.
Body
Provide a JSON in the request body with some or all of the following keys:
{
"name": <string>,
"company": <string>,
"email": <string>,
"phone": <string>,
"skills": [
{
"skill": <string>,
"rating": <int>
},
...
]
}
Keys omitted in the request body will not have their values updated. If the "skills" key is provided in the request body, the skills in the array will entirely replace the user's original skills. User skills that are not in the array will be removed.
Return
- On failure, returns 400.
- On success, returns 200.
Example
# we'll be updating the user with ID=1
$ curl localhost:8080/users/1
{"name":"Breanna Dillon","company":"Jackson Ltd","email":"[email protected]","phone":"+1-924-116-7963","skills":[{"skill":"OpenCV","rating":1},{"skill":"Swift","rating":4}]}
# update some fields
$ curl -X PUT -H "Content-Type: application/json" -d \
'{"company":"", "email":"[email protected]", "phone":"123"}' \
localhost:8080/users/1
{"name":"Breanna Dillon","company":"","email":"[email protected]","phone":"123","skills":[{"skill":"OpenCV","rating":1},{"skill":"Swift","rating":4}]}
# now let's also update some skills
$ curl -X PUT -H "Content-Type: application/json" -d \
'{"email":"[email protected]","skills":[{"skill":"OpenCV","rating":5},{"skill":"Python","rating":3}]}' \
localhost:8080/users/1
{"name":"Breanna Dillon","company":"","email":"[email protected]","phone":"123","skills":[{"skill":"OpenCV","rating":5},{"skill":"Python","rating":3}]}
GET /skills/
Retrieve aggregate information on user skills.
Optional Query Parameters
- min_frequency: only include skills that are possessed by at least min_frequency users (inclusive).
- max_frequency: only include skills that are possessed by at most min_frequency users (inclusive).
Return
- On invalid query parameter values (i.e. not a number), returns 400.
- On other failure, returns 404.
- On success, returns 200 with a body of the following schema:
[
{
"Skill": <string>,
"Count": <int>
},
...
]
Example
$ curl "localhost:8080/skills/"
[{"skill":"Sanic","count":43},{"skill":"React","count":41},{"skill":"Plotly","count":39}, ...
$ curl "localhost:8080/skills/?min_frequency=40"
[{"skill":"Sanic","count":43},{"skill":"React","count":41}]
$ curl "localhost:8080/skills/?min_frequency=19&max_frequency=21"
[{"skill":"Matplotlib","count":21},{"skill":"Aurelia","count":21},{"skill":"Starlette","count":20},{"skill":"Pascal","count":20},{"skill":"Numpy","count":20},{"skill":"Lisp","count":20},{"skill":"Tachyons","count":19}]
Running Tests
Before starting, make sure to stop previous containers (i.e. CTRL+C out of docker compose up).
# this builds the first stage only and will be our dev container. expect some build output
$ docker build . --target builder
...
=> => writing image sha256:<somehashabc> 0.0s
...
# run the image hash from the previous step to start a shell in the container
$ docker run -it <somehashabc>
\#
# run tests in the cmd package
\# go test ./cmd
ok github.com/Aerilate/htn-backend/cmd 0.010s
# display the app help message
\# ./app --help
Serves information on HtN users
Usage:
app [flags]
app [command]
Available Commands:
help Help about any command
migrate Run data migrations on the database
populate Populates the database with data from an input file
serve Starts the server
Flags:
-h, --help help for app
Use "app [command] --help" for more information about a command.
# you wouldn't need to run any of these subcommands because the docker compose file will handle it for you
# but you could technically run `./app migrate` or `./app populate` yourself here
# exit out the container. the container will stop
\# exit
$