From f1f3c3dd5b65974df1b084822d68a8ccb83fcac4 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sun, 3 Nov 2019 15:01:24 -0500 Subject: [PATCH] add backend for entries --- entry/entry.go | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 11 +++- todo.adoc | 8 ++- web/entry.go | 117 ++++++++++++++++++++++++++++++++++-- web/routes.go | 7 ++- web/search.go | 6 ++ 6 files changed, 299 insertions(+), 8 deletions(-) diff --git a/entry/entry.go b/entry/entry.go index d033700..2c2b5e5 100644 --- a/entry/entry.go +++ b/entry/entry.go @@ -1 +1,159 @@ package entry + +import ( + "time" + + "code.chrissexton.org/cws/cabinet/db" + "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" +) + +type Entry struct { + db *db.Database + ID int64 + Slug string + Content string + Tags []string + Created time.Time + Updated time.Time + AuthorID int64 `db:"author_id"` +} + +func PrepareTable(tx *sqlx.Tx) error { + q := `create table if not exists entries ( + id integer primary key, + slug text unique, + content text, + created datetime, + updated datetime, + author_id integer + )` + _, err := tx.Exec(q) + if err != nil { + tx.Rollback() + return err + } + q = `create table if not exists tags ( + id integer primary key, + name text, + entry_id integer, + foreign key(entry_id) references entries(id) + )` + _, err = tx.Exec(q) + if err != nil { + tx.Rollback() + return err + } + return nil +} + +func New(db *db.Database) Entry { + return Entry{ + db: db, + ID: -1, + Created: time.Now(), + Updated: time.Now(), + } +} + +func GetBySlug(db *db.Database, slug string) (Entry, error) { + e := Entry{db: db} + q := `select * from entries where slug = ?` + if err := db.Get(&e, q, slug); err != nil { + return e, err + } + return e, e.populateTags() +} + +func GetByID(db *db.Database, id int64) (Entry, error) { + e := Entry{db: db} + q := `select * from entries where id = ?` + if err := db.Get(&e, q, id); err != nil { + return e, err + } + return e, e.populateTags() +} + +func GetAll(db *db.Database) ([]*Entry, error) { + entries := []*Entry{} + q := `select * from entries` + err := db.Select(&entries, q) + if err != nil { + return nil, err + } + for _, e := range entries { + e.db = db + e.populateTags() + } + return entries, nil +} + +func RemoveBySlug(db *db.Database, slug string) error { + tx, err := db.Begin() + if err != nil { + return err + } + e, err := GetBySlug(db, slug) + if err != nil { + return err + } + q := `delete from tags where entry_id = ?` + if _, err := tx.Exec(q, e.ID); err != nil { + tx.Rollback() + return err + } + q = `delete from entries where entry_id = ?` + if _, err := tx.Exec(q, e.ID); err != nil { + tx.Rollback() + return err + } + return nil +} + +func (e *Entry) populateTags() error { + q := `select name from tags where entry_id = ?` + err := e.db.Select(&e.Tags, q, e.ID) + log.Debug().Interface("entry", e).Msg("populating tags") + return err +} + +func (e *Entry) Update() error { + if e.ID == -1 { + return e.Create() + } + q := `update entries set slug=?, content=?, updated=?, author_id=? where id=?` + _, err := e.db.Exec(q, e.Slug, e.Content, e.Updated.Unix(), e.AuthorID, e.ID) + return err +} + +func (e *Entry) Create() error { + if e.ID != -1 { + return e.Update() + } + tx, err := e.db.Begin() + if err != nil { + return err + } + q := `insert into entries (slug,content,created,updated,author_id) + values (?,?,?,?,?)` + res, err := tx.Exec(q, e.Slug, e.Content, e.Created.Unix(), e.Updated.Unix(), e.AuthorID) + if err != nil { + return err + } + e.ID, err = res.LastInsertId() + if err != nil { + tx.Rollback() + e.ID = -1 + return err + } + for _, t := range e.Tags { + q = `insert into tags (name,entry_id) values (?,?)` + _, err = tx.Exec(q, t, e.ID) + if err != nil { + tx.Rollback() + return err + } + } + tx.Commit() + return nil +} diff --git a/main.go b/main.go index 63dfb70..36a41c7 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "code.chrissexton.org/cws/cabinet/entry" "flag" "os" @@ -16,9 +17,9 @@ import ( ) var ( - dbPath = flag.String("db", "happy.db", "path to db") + dbPath = flag.String("db", "cabinet.db", "path to db") httpAddr = flag.String("httpAddr", "0.0.0.0:8080", "http address") - salt = flag.String("salt", "happy", "salt for IDs") + salt = flag.String("salt", "c4b1n3t", "salt for IDs") minHashLen = flag.Int("minHashLen", 4, "minimum ID hash size") develop = flag.Bool("develop", false, "turn on develop mode") ) @@ -38,6 +39,12 @@ func main() { Msg("could not connect to database") } + tx := db.MustBegin() + if err := entry.PrepareTable(tx); err != nil { + log.Fatal().Err(err).Msg("could not create database") + } + tx.Commit() + s := web.New(*httpAddr, db, box) s.Serve() } diff --git a/todo.adoc b/todo.adoc index b7b6427..8461fd1 100644 --- a/todo.adoc +++ b/todo.adoc @@ -17,5 +17,11 @@ *** [ ] public index/search view * Backend ** [ ] save endpoint +*** [ ] need to generate a slug for entries +*** [ ] add authentication/authorization +*** [ ] convert document to adoc (give format?) +*** [ ] test in frontend +*** [ ] check for unique tags +*** [ ] set some db fields not null ** [ ] search endpoint -* CLI Frontend \ No newline at end of file +* CLI Frontend diff --git a/web/entry.go b/web/entry.go index f981486..e25c2cc 100644 --- a/web/entry.go +++ b/web/entry.go @@ -1,15 +1,124 @@ package web -import "net/http" +import ( + "encoding/json" + "fmt" + "net/http" -func editFile(w http.ResponseWriter, r *http.Request) { + "code.chrissexton.org/cws/cabinet/entry" + "github.com/gorilla/mux" +) +func (web *Web) editEntry(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + slug := vars["slug"] + dec := json.NewDecoder(r.Body) + newEntry := entry.New(web.db) + err := dec.Decode(&newEntry) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + + oldEntry, err := entry.GetBySlug(web.db, slug) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + + newEntry.ID = oldEntry.ID + err = newEntry.Update() + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + resp, err := json.Marshal(newEntry) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Header().Set("content-type", "application/json") + fmt.Fprint(w, string(resp)) } -func newFile(w http.ResponseWriter, r *http.Request) { +func (web *Web) newEntry(w http.ResponseWriter, r *http.Request) { + dec := json.NewDecoder(r.Body) + newEntry := entry.New(web.db) + err := dec.Decode(&newEntry) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + err = newEntry.Create() + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + resp, err := json.Marshal(newEntry) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Header().Set("content-type", "application/json") + fmt.Fprint(w, string(resp)) } -func getFile(w http.ResponseWriter, r *http.Request) { +func (web *Web) allEntries(w http.ResponseWriter, r *http.Request) { + entries, err := entry.GetAll(web.db) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + resp, err := json.Marshal(entries) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Header().Set("content-type", "application/json") + fmt.Fprint(w, string(resp)) +} + +func (web *Web) getEntry(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + slug := vars["slug"] + + entry, err := entry.GetBySlug(web.db, slug) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + + resp, err := json.Marshal(entry) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Header().Set("content-type", "application/json") + fmt.Fprint(w, string(resp)) +} + +func (web *Web) removeEntry(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + slug := vars["slug"] + + err := entry.RemoveBySlug(web.db, slug) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.WriteHeader(200) } diff --git a/web/routes.go b/web/routes.go index fe84955..04950d5 100644 --- a/web/routes.go +++ b/web/routes.go @@ -41,7 +41,12 @@ func New(addr string, db *db.Database, box *packr.Box) *Web { func (web *Web) routeSetup() http.Handler { r := mux.NewRouter() - //api := r.PathPrefix("/v1/").Subrouter() + api := r.PathPrefix("/v1/").Subrouter() + api.HandleFunc("/entries", web.allEntries).Methods(http.MethodGet) + api.HandleFunc("/entries", web.newEntry).Methods(http.MethodPost) + api.HandleFunc("/entries", web.removeEntry).Methods(http.MethodDelete) + api.HandleFunc("/entries/{slug}", web.editEntry).Methods(http.MethodPut) + api.HandleFunc("/entries/{slug}", web.getEntry).Methods(http.MethodGet) r.PathPrefix("/").HandlerFunc(web.indexHandler("/index.html")) loggedRouter := handlers.LoggingHandler(os.Stdout, r) return loggedRouter diff --git a/web/search.go b/web/search.go index efb3895..bee58e1 100644 --- a/web/search.go +++ b/web/search.go @@ -1 +1,7 @@ package web + +import "net/http" + +func search(w http.ResponseWriter, r *http.Request) { + +}