add backend for entries
This commit is contained in:
parent
f7dfee399e
commit
f1f3c3dd5b
158
entry/entry.go
158
entry/entry.go
|
@ -1 +1,159 @@
|
||||||
package entry
|
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
|
||||||
|
}
|
||||||
|
|
11
main.go
11
main.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.chrissexton.org/cws/cabinet/entry"
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -16,9 +17,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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")
|
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")
|
minHashLen = flag.Int("minHashLen", 4, "minimum ID hash size")
|
||||||
develop = flag.Bool("develop", false, "turn on develop mode")
|
develop = flag.Bool("develop", false, "turn on develop mode")
|
||||||
)
|
)
|
||||||
|
@ -38,6 +39,12 @@ func main() {
|
||||||
Msg("could not connect to database")
|
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 := web.New(*httpAddr, db, box)
|
||||||
s.Serve()
|
s.Serve()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,11 @@
|
||||||
*** [ ] public index/search view
|
*** [ ] public index/search view
|
||||||
* Backend
|
* Backend
|
||||||
** [ ] save endpoint
|
** [ ] 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
|
** [ ] search endpoint
|
||||||
* CLI Frontend
|
* CLI Frontend
|
121
web/entry.go
121
web/entry.go
|
@ -1,15 +1,124 @@
|
||||||
package web
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFile(w http.ResponseWriter, r *http.Request) {
|
oldEntry, err := entry.GetBySlug(web.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFile(w http.ResponseWriter, r *http.Request) {
|
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 (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 (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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,12 @@ func New(addr string, db *db.Database, box *packr.Box) *Web {
|
||||||
|
|
||||||
func (web *Web) routeSetup() http.Handler {
|
func (web *Web) routeSetup() http.Handler {
|
||||||
r := mux.NewRouter()
|
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"))
|
r.PathPrefix("/").HandlerFunc(web.indexHandler("/index.html"))
|
||||||
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
|
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
|
||||||
return loggedRouter
|
return loggedRouter
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func search(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue