Coopon
Coopon is a web service that allows users to create, read, update and destroy discount coupons using a simple REST API.
It implements a custom migration engine for the SQLite database and a very simple custom REST route multiplexer.
Docker quickstart
docker run -p 1337:1337 donotnoot/coopon
You can now acccess the API at http://localhost:1337
.
Building and running
Make sure you have Git, GNU Make, and Go >= 1.11 installed on your system.
git clone https://github.com/donotnoot/coopon.git
cd coopon
make
./coop migrate # Run migrations
./coop serve # Start HTTP Server
Then, using your favorite HTTP client (in this case, httpie on a different tty)
http post http://localhost:1337/coupons brand=Tesco name="Save £20 at Tesco" value:=1 valueType:=0 expiry="2020-01-12T00:00:00Z"
http get localhost:1337/coupons
http get localhost:1337/coupons/1
http patch localhost:1337/coupons/1 brand=Sainsburys
http delete localhost:1337/coupons/1
To get more records to play with, you can also run the seed
./coop migrate # Make sure migrations have been executed first
./coop seed # Seed the DB
./coop serve # Serve the app
Then, on a different tty:
http get localhost:1337/coupons
http get localhost:1337/coupons expiresAfter==2021-12-01 00:00:00Z
GET /coupons
parameters
The coupon list endpoint accepts the following GET params:
brand
, name
: String filters for brands and coupon names. Uses simple SQL LIKE
%
matching.
minSaving
, maxSaving
: Integer filters for the coupon value
after
, before
: Date filters for the created_at value.
expiresAfter
, expiresBefore
: Date filters for the expires_at value.
Dates must be strings like YYYY-MM-DDTHH:MM:SSZ
.
Migration engine
The package migrations implements a very simple migration engine.
Refer to godoc for documentation.
Migration engine example
Given this directory structure:
main.go
migrations/
0001-create-schema.sql
package main
import (
"os"
"database/sql"
"github.com/donotnoot/coopon/migrations"
)
func main() {
var db *sql.DB
/*
SQLite init code goes here
*/
switch os.Args[1] {
case "migrate":
log.Println("")
e, err := migrations.Run(db, "./migrations")
if err != nil {
log.Fatal(err)
}
log.Printf("%d migrations have been executed", e)
os.Exit(0)
}
}
Every time a new migration is added, you would just run ./main migrate
to add the new migrations to the DB.
Router
The router package implements a very simple router that is compatible with the standard http server library.
Currently, the router implements a global middleware handler, integer route parameters and a simple dependency container
Refere to godoc for documentation
Router example
This simple program responds to CORS preflights and says hi to whoever it calls the root path, as long as the name of the person calling is an integer number.
package main
import (
"fmt"
"io"
"log"
"net/http"
"github.com/donotnoot/coopon/router"
)
func main() {
router := &router.Router{}
// Add CORS middleware
router.RegisterMiddleware([]string{"OPTIONS"}, CORSHandler)
// Add route
router.RegisterRoute("GET", "/hello/{}", Hello)
// Serve!!
err := http.ListenAndServe(":8080", router)
if err != nil {
log.Fatal(err)
}
}
func Hello(w http.ResponseWriter, req *http.Request, p *router.Params, r *router.Router) {
fmt.Fprintf(w, `{"salutation":"hello, %d!"}`, (*p)[0])
}
// CORSHandler adds very insecure Access-Control headers to the response.
func CORSHandler(w http.ResponseWriter, req *http.Request) (respondNow bool) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "*")
w.Header().Set("Access-Control-Allow-Methods", "*")
io.WriteString(w, "Preflight OK")
return true
}