Skip to content
Snippets Groups Projects
Commit cb0a40c0 authored by meskio's avatar meskio :tent:
Browse files

Simple API server with support for members

parents
No related branches found
No related tags found
No related merge requests found
package api
import (
"github.com/gorilla/mux"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type api struct {
db *gorm.DB
}
func initDB(dbPath string) (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
return nil, err
}
db.AutoMigrate(&Member{})
return db, err
}
func Init(dbPath string, r *mux.Router) error {
db, err := initDB(dbPath)
if err != nil {
return err
}
a := api{db}
r.HandleFunc("/member", a.ListMembers).Methods("GET")
r.HandleFunc("/member", a.AddMember).Methods("POST")
r.HandleFunc("/member/{num:[0-9]+}", a.GetMember).Methods("GET")
r.HandleFunc("/member/{num:[0-9]+}", a.UpdateMember).Methods("PUT")
r.HandleFunc("/member/{num:[0-9]+}", a.DeleteMember).Methods("DELETE")
return nil
}
package api
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path"
"testing"
"github.com/gorilla/mux"
)
func TestInit(t *testing.T) {
tapi := newTestAPI(t)
defer tapi.close()
resp := tapi.do("GET", "/", nil, nil)
if resp.StatusCode != http.StatusNotFound {
t.Error("Expected not found", resp.Status, resp.Body)
}
}
type testAPI struct {
t *testing.T
baseURL string
client *http.Client
server *httptest.Server
testPath string
}
func newTestAPI(t *testing.T) *testAPI {
testPath, err := ioutil.TempDir(os.TempDir(), "cicer-test-")
if err != nil {
t.Fatal("Tempdir error:", err)
}
dbPath := path.Join(testPath, "test.db")
r := mux.NewRouter()
err = Init(dbPath, r)
if err != nil {
t.Fatal("Init error:", err)
}
server := httptest.NewServer(r)
return &testAPI{t, server.URL, &http.Client{}, server, testPath}
}
func (ta *testAPI) do(method string, url string, body interface{}, respBody interface{}) *http.Response {
var reader io.Reader
if body != nil {
var w io.WriteCloser
reader, w = io.Pipe()
go func() {
json.NewEncoder(w).Encode(body)
w.Close()
}()
}
req, err := http.NewRequest(method, ta.baseURL+url, reader)
if err != nil {
ta.t.Fatal("Can't build request", method, url, err)
}
resp, err := ta.client.Do(req)
if err != nil {
ta.t.Fatal("HTTP query failed", method, url, err)
}
if respBody != nil {
err = json.NewDecoder(resp.Body).Decode(respBody)
if err != nil {
ta.t.Fatal("Can't decode resp", method, url, err)
}
}
return resp
}
func (ta *testAPI) close() {
ta.server.Close()
os.RemoveAll(ta.testPath)
}
package api
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"gorm.io/gorm"
)
type Member struct {
gorm.Model `json:"-"`
Num int `json:"num"`
Name string `json:"name"`
Email string `json:"email"`
Balance int `json:"balance"`
Role string `json:"role"`
}
func (a *api) AddMember(w http.ResponseWriter, req *http.Request) {
var member Member
err := json.NewDecoder(req.Body).Decode(&member)
if err != nil {
log.Printf("Can't create member: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
err = a.db.Create(&member).Error
if err != nil {
log.Printf("Can't create member: %v\n%v", err, member)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
err = json.NewEncoder(w).Encode(member)
if err != nil {
log.Printf("Can't encode added member: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (a *api) ListMembers(w http.ResponseWriter, req *http.Request) {
var members []Member
err := a.db.Find(&members).Error
if err != nil {
log.Printf("Can't list members: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(members)
if err != nil {
log.Printf("Can't encode members: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (a *api) GetMember(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
var member Member
err := a.db.Where("num = ?", vars["num"]).First(&member).Error
if err != nil {
if err.Error() == "record not found" {
w.WriteHeader(http.StatusNotFound)
return
}
log.Printf("Can't get member %s: %v", vars["num"], err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(member)
if err != nil {
log.Printf("Can't encode member: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (a *api) DeleteMember(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
err := a.db.Where("num = ?", vars["num"]).Delete(&Member{}).Error
if err != nil {
log.Printf("Can't delete member %s: %v", vars["num"], err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func (a *api) UpdateMember(w http.ResponseWriter, req *http.Request) {
var member Member
err := json.NewDecoder(req.Body).Decode(&member)
if err != nil {
log.Printf("Can't decode memeber: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
vars := mux.Vars(req)
var dbMember Member
err = a.db.Where("num = ?", vars["num"]).First(&dbMember).Error
if err != nil {
if err.Error() == "record not found" {
w.WriteHeader(http.StatusNotFound)
return
}
log.Printf("Can't get member %s: %v", vars["num"], err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if member.Num != 0 {
dbMember.Num = member.Num
}
if member.Name != "" {
dbMember.Name = member.Name
}
if member.Email != "" {
dbMember.Email = member.Email
}
if member.Balance >= 0 {
dbMember.Balance = member.Balance
}
err = a.db.Save(&dbMember).Error
if err != nil {
log.Printf("Can't update member %s: %v %v", vars["num"], err, member)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
err = json.NewEncoder(w).Encode(dbMember)
if err != nil {
log.Printf("Can't encode updated memeber: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
package api
import (
"net/http"
"testing"
)
func TestAddList(t *testing.T) {
tapi := newTestAPI(t)
defer tapi.close()
member := Member{
Num: 10,
Name: "foo",
Email: "foo@example.com",
Balance: 2000,
}
resp := tapi.do("POST", "/member", member, nil)
if resp.StatusCode != http.StatusCreated {
t.Fatal("Can't create member:", resp.Status)
}
var members []Member
resp = tapi.do("GET", "/member", nil, &members)
if resp.StatusCode != http.StatusOK {
t.Fatal("Can't get members:", resp.Status)
}
if len(members) != 1 {
t.Fatal("Wrong number of members", len(members), members)
}
if members[0].Name != "foo" {
t.Error("Wrong name:", members[0].Name)
}
if members[0].Email != "foo@example.com" {
t.Error("Wrong email:", members[0].Email)
}
}
func TestGetDelete(t *testing.T) {
tapi := newTestAPI(t)
defer tapi.close()
resp := tapi.do("GET", "/member/10", nil, nil)
if resp.StatusCode != http.StatusNotFound {
t.Error("Expected not found:", resp.Status, resp.Body)
}
member := Member{
Num: 10,
Name: "foo",
Email: "foo@example.com",
Balance: 2000,
}
resp = tapi.do("POST", "/member", member, nil)
if resp.StatusCode != http.StatusCreated {
t.Fatal("Can't create member:", resp.Status)
}
var gotMember Member
resp = tapi.do("GET", "/member/10", nil, &gotMember)
if resp.StatusCode != http.StatusOK {
t.Error("Can't find the member:", resp.Status)
}
if gotMember.Num != 10 {
t.Error("Wrong member:", gotMember.Num)
}
resp = tapi.do("DELETE", "/member/10", nil, nil)
if resp.StatusCode != http.StatusOK {
t.Error("Can't find the member:", resp.Status)
}
}
func TestPut(t *testing.T) {
tapi := newTestAPI(t)
defer tapi.close()
member := Member{
Num: 10,
Name: "foo",
Email: "foo@example.com",
Balance: 2000,
}
resp := tapi.do("POST", "/member", member, nil)
if resp.StatusCode != http.StatusCreated {
t.Fatal("Can't create member:", resp.Status)
}
member.Balance = 1000
resp = tapi.do("PUT", "/member/10", member, nil)
if resp.StatusCode != http.StatusAccepted {
t.Fatal("Can't update member:", resp.Status)
}
var gotMember Member
resp = tapi.do("GET", "/member/10", nil, &gotMember)
if resp.StatusCode != http.StatusOK {
t.Error("Can't find the member:", resp.Status)
}
if gotMember.Balance != 1000 {
t.Error("Wrong balance:", gotMember)
}
}
go.mod 0 → 100644
go.sum 0 → 100644
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-sqlite3 v1.14.2 h1:A2EQLwjYf/hfYaM20FVjs1UewCTTFR7RmjEHkLjldIA=
github.com/mattn/go-sqlite3 v1.14.2/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/olivere/env v1.1.0 h1:owp/uwMwhru5668JjMDp8UTG3JGT27GTCk4ufYQfaTw=
github.com/olivere/env v1.1.0/go.mod h1:zaoXy53SjZfxqZBGiGrZCkuVLYPdwrc+vArPuUVhJdQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gorm.io/driver/sqlite v1.1.2 h1:6LsQVSO93WU4Xv2NTwIk2jE3bbKBLgMGmertBleuSTE=
gorm.io/driver/sqlite v1.1.2/go.mod h1:Gf7dk8OqOyHdDv/LH/z4A1xCHKYibVLzF9bfOwZA0ZM=
gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.1 h1:+hOwlHDqvqmBIMflemMVPLJH7tZYK4RxFDBHEfJTup0=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
main.go 0 → 100644
package main
import (
"flag"
"log"
"net/http"
"0xacab.org/meskio/cicer/api"
"github.com/gorilla/mux"
"github.com/olivere/env"
)
func main() {
var (
dbPath = flag.String("db-path", env.String("./test.db", "DB_PATH"), "Path where the sqlite will be located")
addr = flag.String("addr", env.String(":8080", "HTTP_ADDR", "ADDR"), "Address where the http server will bind")
)
flag.Parse()
r := mux.NewRouter()
apiRouter := r.PathPrefix("/api/").Subrouter()
err := api.Init(*dbPath, apiRouter)
if err != nil {
log.Panicln("Can't open the database:", err)
}
log.Fatal(http.ListenAndServe(*addr, r))
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment