go: refactor
* Move web stuff out to respective files * Move DB stuff to its own package * Move user stuff to its own package
This commit is contained in:
parent
b5ed7342a0
commit
a8560961ba
|
@ -0,0 +1,84 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import "github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
*sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(path string) (*Database, error) {
|
||||||
|
d, err := sqlx.Open("sqlite3", path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Database{d}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) MakeDB() error {
|
||||||
|
q := `create table if not exists users (
|
||||||
|
id integer primary key,
|
||||||
|
email text unique,
|
||||||
|
verification integer not null,
|
||||||
|
dateCreated integer,
|
||||||
|
lastLogin integer
|
||||||
|
)`
|
||||||
|
if _, err := d.Exec(q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q = `create table if not exists mood_categories (
|
||||||
|
id integer primary key,
|
||||||
|
name text
|
||||||
|
)`
|
||||||
|
if _, err := d.Exec(q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q = `create table if not exists mood_category_texts (
|
||||||
|
id integer primary key,
|
||||||
|
mood_category_id integer,
|
||||||
|
key text,
|
||||||
|
value integer,
|
||||||
|
foreign key(mood_category_id) references mood_categories(id)
|
||||||
|
|
||||||
|
)`
|
||||||
|
if _, err := d.Exec(q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q = `create table if not exists moods (
|
||||||
|
id integer primary key,
|
||||||
|
user_id integer,
|
||||||
|
mood_category_id integer,
|
||||||
|
value integer,
|
||||||
|
time integer,
|
||||||
|
foreign key(user_id) references users(id),
|
||||||
|
foreign key(mood_category_id) references mood_categories(id)
|
||||||
|
)`
|
||||||
|
if _, err := d.Exec(q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
q = `select count(*) from mood_category_texts mct inner join mood_categories mc on mct.mood_category_id=mc.id`
|
||||||
|
var count int
|
||||||
|
if err := d.Get(&count, q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return d.populateMoods()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) populateMoods() error {
|
||||||
|
tx := d.MustBegin()
|
||||||
|
res := tx.MustExec(`insert into mood_categories (name) values ('happy')`)
|
||||||
|
id, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.MustExec(`insert into mood_category_texts (mood_category_id,key,value) values (?,?,?)`,
|
||||||
|
id, "😄", 1)
|
||||||
|
tx.MustExec(`insert into mood_category_texts (mood_category_id,key,value) values (?,?,?)`,
|
||||||
|
id, "😐", 0)
|
||||||
|
tx.MustExec(`insert into mood_category_texts (mood_category_id,key,value) values (?,?,?)`,
|
||||||
|
id, "😟", -1)
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ export default {
|
||||||
return axios.get("/v1/moods", {
|
return axios.get("/v1/moods", {
|
||||||
headers: {
|
headers: {
|
||||||
'X-user-id': userInfo.ID,
|
'X-user-id': userInfo.ID,
|
||||||
'X-user-validation': userInfo.Validation
|
'X-user-verification': userInfo.Verification
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@ export default {
|
||||||
return axios.post("/v1/moods", mood, {
|
return axios.post("/v1/moods", mood, {
|
||||||
headers: {
|
headers: {
|
||||||
'X-user-id': userInfo.ID,
|
'X-user-id': userInfo.ID,
|
||||||
'X-user-validation': userInfo.Validation
|
'X-user-verification': userInfo.Verification
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -24,7 +24,7 @@ export default {
|
||||||
return axios.get("/v1/user/info", {
|
return axios.get("/v1/user/info", {
|
||||||
headers: {
|
headers: {
|
||||||
'X-user-id': userInfo.ID,
|
'X-user-id': userInfo.ID,
|
||||||
'X-user-validation': userInfo.Validation
|
'X-user-verification': userInfo.Verification
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ Vue.use(Vuex);
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
errs: [],
|
errs: [],
|
||||||
userInfo: {ID: "", Validation: ""},
|
userInfo: {ID: "", Verification: ""},
|
||||||
moods: [],
|
moods: [],
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package mood
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/db"
|
||||||
|
"code.chrissexton.org/cws/happy/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mood struct {
|
||||||
|
db *db.Database
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *db.Database) *Mood {
|
||||||
|
return &Mood{db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetMoodsResponse struct {
|
||||||
|
CategoryID int64 `db:"category_id"`
|
||||||
|
CategoryName string `db:"category_name"`
|
||||||
|
Key string
|
||||||
|
Value int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mood) RecordMood(mood GetMoodsResponse, who user.User) error {
|
||||||
|
q := `insert into moods (user_id,mood_category_id,value,time) values (?,?,?,?)`
|
||||||
|
_, err := m.db.Exec(q, who.ID, mood.CategoryID, mood.Value, time.Now().Unix())
|
||||||
|
return err
|
||||||
|
}
|
358
serve.go
358
serve.go
|
@ -1,31 +1,28 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
"code.chrissexton.org/cws/happy/web"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/db"
|
||||||
|
|
||||||
packr "github.com/gobuffalo/packr/v2"
|
packr "github.com/gobuffalo/packr/v2"
|
||||||
|
|
||||||
"code.chrissexton.org/cws/happy/email"
|
"code.chrissexton.org/cws/happy/email"
|
||||||
"code.chrissexton.org/cws/happy/user"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
hashids "github.com/speps/go-hashids"
|
hashids "github.com/speps/go-hashids"
|
||||||
"github.com/stretchr/graceful"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
dbPath = flag.String("db", "happy.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", "happy", "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")
|
||||||
|
@ -67,350 +64,25 @@ func main() {
|
||||||
box := packr.New("dist", "frontend/dist")
|
box := packr.New("dist", "frontend/dist")
|
||||||
log.Debug().Strs("dirlist", box.List()).Msg("packr made")
|
log.Debug().Strs("dirlist", box.List()).Msg("packr made")
|
||||||
|
|
||||||
s := server{
|
db, err := db.New(*dbPath)
|
||||||
addr: "0.0.0.0:8080",
|
|
||||||
assetPath: "pub",
|
|
||||||
salt: *salt,
|
|
||||||
box: box,
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := sqlx.Connect("sqlite3", "happy.db")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().
|
log.Fatal().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("could not connect to database")
|
Msg("could not connect to database")
|
||||||
}
|
}
|
||||||
s.db = db
|
|
||||||
hd := hashids.NewData()
|
hd := hashids.NewData()
|
||||||
hd.Salt = s.salt
|
hd.Salt = *salt
|
||||||
hd.MinLength = *minHashLen
|
hd.MinLength = *minHashLen
|
||||||
s.h, _ = hashids.NewWithData(hd)
|
h, _ := hashids.NewWithData(hd)
|
||||||
if err := s.makeDB(); err != nil {
|
|
||||||
log.Fatal().
|
var mailClient *email.EMailClient
|
||||||
Err(err).
|
|
||||||
Msg("could not create database")
|
|
||||||
}
|
|
||||||
if validateMail() {
|
if validateMail() {
|
||||||
log.Debug().Msg("sending mail")
|
log.Debug().Msg("sending mail")
|
||||||
s.email = email.New(*mailAddr, *mailPort, *mailUser, *mailPass)
|
mailClient = email.New(*mailAddr, *mailPort, *mailUser, *mailPass)
|
||||||
u, _ := s.NewUser()
|
|
||||||
s.email.SendNewUserMail("chris@chrissexton.org", u, "http://happy.chrissexton.org")
|
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Msg("mail disabled")
|
log.Debug().Msg("mail disabled")
|
||||||
}
|
}
|
||||||
s.serve()
|
s := web.New(*httpAddr, "pub", db, *salt, h, box, mailClient)
|
||||||
}
|
s.Serve()
|
||||||
|
|
||||||
type server struct {
|
|
||||||
addr string
|
|
||||||
assetPath string
|
|
||||||
db *sqlx.DB
|
|
||||||
salt string
|
|
||||||
h *hashids.HashID
|
|
||||||
box *packr.Box
|
|
||||||
email *email.EMailClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) makeDB() error {
|
|
||||||
q := `create table if not exists users (
|
|
||||||
id integer primary key,
|
|
||||||
email text unique,
|
|
||||||
verification integer not null,
|
|
||||||
dateCreated integer,
|
|
||||||
lastLogin integer
|
|
||||||
)`
|
|
||||||
if _, err := s.db.Exec(q); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q = `create table if not exists mood_categories (
|
|
||||||
id integer primary key,
|
|
||||||
name text
|
|
||||||
)`
|
|
||||||
if _, err := s.db.Exec(q); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q = `create table if not exists mood_category_texts (
|
|
||||||
id integer primary key,
|
|
||||||
mood_category_id integer,
|
|
||||||
key text,
|
|
||||||
value integer,
|
|
||||||
foreign key(mood_category_id) references mood_categories(id)
|
|
||||||
|
|
||||||
)`
|
|
||||||
if _, err := s.db.Exec(q); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q = `create table if not exists moods (
|
|
||||||
id integer primary key,
|
|
||||||
user_id integer,
|
|
||||||
mood_category_id integer,
|
|
||||||
value integer,
|
|
||||||
time integer,
|
|
||||||
foreign key(user_id) references users(id),
|
|
||||||
foreign key(mood_category_id) references mood_categories(id)
|
|
||||||
)`
|
|
||||||
if _, err := s.db.Exec(q); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
q = `select count(*) from mood_category_texts mct inner join mood_categories mc on mct.mood_category_id=mc.id`
|
|
||||||
var count int
|
|
||||||
if err := s.db.Get(&count, q); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count == 0 {
|
|
||||||
return s.populateMoods()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) populateMoods() error {
|
|
||||||
tx := s.db.MustBegin()
|
|
||||||
res := tx.MustExec(`insert into mood_categories (name) values ('happy')`)
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tx.MustExec(`insert into mood_category_texts (mood_category_id,key,value) values (?,?,?)`,
|
|
||||||
id, "😄", 1)
|
|
||||||
tx.MustExec(`insert into mood_category_texts (mood_category_id,key,value) values (?,?,?)`,
|
|
||||||
id, "😐", 0)
|
|
||||||
tx.MustExec(`insert into mood_category_texts (mood_category_id,key,value) values (?,?,?)`,
|
|
||||||
id, "😟", -1)
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) recordMood(mood getMoodsResponse, who user.User) error {
|
|
||||||
q := `insert into moods (user_id,mood_category_id,value,time) values (?,?,?,?)`
|
|
||||||
_, err := s.db.Exec(q, who.ID, mood.CategoryID, mood.Value, time.Now().Unix())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) NewUser() (user.User, error) {
|
|
||||||
uid := user.New(s.db, s.salt, s.h)
|
|
||||||
q := `insert into users (verification,dateCreated) values (?, ?)`
|
|
||||||
res, err := s.db.Exec(q, uid.Verification, uid.DateCreated)
|
|
||||||
if err != nil {
|
|
||||||
return uid, err
|
|
||||||
}
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return uid, err
|
|
||||||
}
|
|
||||||
uid.ID = id
|
|
||||||
return uid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) FromStr(uid, verification string) (user.User, error) {
|
|
||||||
id := user.New(s.db, s.salt, s.h)
|
|
||||||
if uid == "" || verification == "" {
|
|
||||||
return id, fmt.Errorf("user ID and verification not given.")
|
|
||||||
}
|
|
||||||
idInt, err := s.h.DecodeInt64WithError(uid)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
q := `select id,email,verification,datecreated,lastlogin from users where id=?`
|
|
||||||
if err := s.db.Get(&id, q, idInt[0]); err != nil {
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Msg("unable to select")
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
verify, err := s.h.EncodeInt64([]int64{id.Verification})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Str("verify", verify).
|
|
||||||
Str("id.Verification", verification).
|
|
||||||
Msg("unable to encode")
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
if verify != verification {
|
|
||||||
log.Debug().
|
|
||||||
Str("verify", verify).
|
|
||||||
Str("id.Verification", verification).
|
|
||||||
Msg("verification mismatch")
|
|
||||||
return id, fmt.Errorf("Invalid verification token")
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegisterResponse struct {
|
|
||||||
ID string
|
|
||||||
DateCreated int64
|
|
||||||
Validation string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) handlerRegisterCode(w http.ResponseWriter, r *http.Request) {
|
|
||||||
uid, err := s.NewUser()
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
log.Error().Err(err).Msg("error from NewUserID")
|
|
||||||
fmt.Fprintf(w, "Error registering user: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
log.Error().Err(err).Msg("error converting date")
|
|
||||||
fmt.Fprintf(w, "Error registering user: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp := RegisterResponse{
|
|
||||||
ID: uid.String(),
|
|
||||||
DateCreated: uid.DateCreated.Int64,
|
|
||||||
}
|
|
||||||
if *develop {
|
|
||||||
resp.Validation, _ = s.h.EncodeInt64([]int64{uid.Verification})
|
|
||||||
}
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
log.Error().Err(err).Msg("error from json.Marshal")
|
|
||||||
fmt.Fprintf(w, "Error registering user: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s", string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
type getMoodsResponse struct {
|
|
||||||
CategoryID int64 `db:"category_id"`
|
|
||||||
CategoryName string `db:"category_name"`
|
|
||||||
Key string
|
|
||||||
Value int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) getMoods(w http.ResponseWriter, r *http.Request) {
|
|
||||||
log.Debug().Interface("req", r).Msg("getMoods")
|
|
||||||
q := `select mc.id category_id,mc.name category_name,mct.key,mct.value
|
|
||||||
from mood_categories mc
|
|
||||||
inner join mood_category_texts mct
|
|
||||||
on mc.id=mct.mood_category_id`
|
|
||||||
recs := []getMoodsResponse{}
|
|
||||||
err := s.db.Select(&recs, q)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("could not retrieve mood categories and texts")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
fmt.Fprintf(w, "Error getting moods: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, err := json.Marshal(recs)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error from json.Marshal")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
fmt.Fprintf(w, "Error getting moods: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s", string(resp))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) checkUser(w http.ResponseWriter, r *http.Request) {
|
|
||||||
uid := r.Header.Get("X-user-id")
|
|
||||||
verify := r.Header.Get("X-user-validation")
|
|
||||||
log.Debug().
|
|
||||||
Str("uid", uid).
|
|
||||||
Str("verify", verify).
|
|
||||||
Msg("checkUser")
|
|
||||||
user, err := s.FromStr(uid, verify)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("user not known")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
fmt.Fprint(w, "User not known")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
j, err := json.Marshal(user)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
log.Error().Err(err).Msg("could not marshal user")
|
|
||||||
fmt.Fprintf(w, "%s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) handleMood(w http.ResponseWriter, r *http.Request) {
|
|
||||||
uid := r.Header.Get("X-user-id")
|
|
||||||
verify := r.Header.Get("X-user-validation")
|
|
||||||
log.Debug().
|
|
||||||
Str("uid", uid).
|
|
||||||
Str("verify", verify).
|
|
||||||
Msg("handleMood")
|
|
||||||
dec := json.NewDecoder(r.Body)
|
|
||||||
happyReq := getMoodsResponse{}
|
|
||||||
err := dec.Decode(&happyReq)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("error with happy")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
fmt.Fprintf(w, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug().
|
|
||||||
Interface("mood", happyReq).
|
|
||||||
Msg("mood")
|
|
||||||
user, err := s.FromStr(uid, verify)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Msg("error getting user")
|
|
||||||
w.WriteHeader(403)
|
|
||||||
fmt.Fprintf(w, "Error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := s.recordMood(happyReq, user); err != nil {
|
|
||||||
log.Error().Err(err).Msg("error saving mood")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
fmt.Fprintf(w, "Error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "ok")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) indexHandler(entryPoint string) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
p := path.Clean(r.URL.Path)
|
|
||||||
|
|
||||||
if s.box.Has(p) && !s.box.HasDir(p) {
|
|
||||||
f, err := s.box.Find(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Error finding file")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
w.Write(f)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.box.HasDir(p) && s.box.Has(path.Join(p, "index.html")) {
|
|
||||||
f, err := s.box.Find(path.Join(p, "index.html"))
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Error finding file")
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
w.Write(f)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f, err := s.box.Find(p); err != nil {
|
|
||||||
w.Write(f)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) routeSetup() *mux.Router {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
api := r.PathPrefix("/v1/").Subrouter()
|
|
||||||
api.HandleFunc("/moods", s.getMoods).Methods("GET")
|
|
||||||
api.HandleFunc("/moods", s.handleMood).Methods("POST")
|
|
||||||
api.HandleFunc("/user/code", s.handlerRegisterCode).Methods("GET")
|
|
||||||
api.HandleFunc("/user/info", s.checkUser).Methods("GET")
|
|
||||||
r.PathPrefix("/").HandlerFunc(s.indexHandler("/index.html"))
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) serve() {
|
|
||||||
middle := s.routeSetup()
|
|
||||||
log.Info().Str("addr", "http://"+s.addr).Msg("serving HTTP")
|
|
||||||
graceful.Run(s.addr, 10*time.Second, middle)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/speps/go-hashids"
|
"code.chrissexton.org/cws/happy/db"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/speps/go-hashids"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
db *sqlx.DB
|
db *db.Database
|
||||||
|
|
||||||
ID int64
|
ID int64
|
||||||
Email sql.NullString
|
Email sql.NullString
|
||||||
|
@ -23,7 +23,7 @@ type User struct {
|
||||||
str string
|
str string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(db *sqlx.DB, salt string, h *hashids.HashID) User {
|
func New(db *db.Database, salt string, h *hashids.HashID) User {
|
||||||
u := User{
|
u := User{
|
||||||
db: db,
|
db: db,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/speps/go-hashids"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/db"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lol
|
||||||
|
|
||||||
|
type UserFactory struct {
|
||||||
|
db *db.Database
|
||||||
|
salt string
|
||||||
|
h *hashids.HashID
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFactory(db *db.Database, salt string, h *hashids.HashID) *UserFactory {
|
||||||
|
return &UserFactory{
|
||||||
|
db: db,
|
||||||
|
salt: salt,
|
||||||
|
h: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uf *UserFactory) NewUser() (User, error) {
|
||||||
|
uid := New(uf.db, uf.salt, uf.h)
|
||||||
|
q := `insert into users (verification,dateCreated) values (?, ?)`
|
||||||
|
res, err := uf.db.Exec(q, uid.Verification, uid.DateCreated)
|
||||||
|
if err != nil {
|
||||||
|
return uid, err
|
||||||
|
}
|
||||||
|
id, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return uid, err
|
||||||
|
}
|
||||||
|
uid.ID = id
|
||||||
|
return uid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uf *UserFactory) FromStr(uid, verification string) (User, error) {
|
||||||
|
id := New(uf.db, uf.salt, uf.h)
|
||||||
|
if uid == "" || verification == "" {
|
||||||
|
return id, fmt.Errorf("user ID and verification not given.")
|
||||||
|
}
|
||||||
|
idInt, err := uf.h.DecodeInt64WithError(uid)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
q := `select id,email,verification,datecreated,lastlogin from users where id=?`
|
||||||
|
if err := uf.db.Get(&id, q, idInt[0]); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("unable to select")
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
verify, err := uf.h.EncodeInt64([]int64{id.Verification})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("verify", verify).
|
||||||
|
Str("id.Verification", verification).
|
||||||
|
Msg("unable to encode")
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
if verify != verification {
|
||||||
|
log.Debug().
|
||||||
|
Str("verify", verify).
|
||||||
|
Str("id.Verification", verification).
|
||||||
|
Msg("verification mismatch")
|
||||||
|
return id, fmt.Errorf("Invalid verification token")
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/mood"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (web *Web) getMoods(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Debug().Interface("req", r).Msg("getMoods")
|
||||||
|
q := `select mc.id category_id,mc.name category_name,mct.key,mct.value
|
||||||
|
from mood_categories mc
|
||||||
|
inner join mood_category_texts mct
|
||||||
|
on mc.id=mct.mood_category_id`
|
||||||
|
recs := []mood.GetMoodsResponse{}
|
||||||
|
err := web.db.Select(&recs, q)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("could not retrieve mood categories and texts")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprintf(w, "Error getting moods: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := json.Marshal(recs)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error from json.Marshal")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprintf(w, "Error getting moods: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s", string(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) handleMood(w http.ResponseWriter, r *http.Request) {
|
||||||
|
uid := r.Header.Get("X-user-id")
|
||||||
|
verify := r.Header.Get("X-user-verification")
|
||||||
|
log.Debug().
|
||||||
|
Str("uid", uid).
|
||||||
|
Str("verify", verify).
|
||||||
|
Msg("handleMood")
|
||||||
|
dec := json.NewDecoder(r.Body)
|
||||||
|
happyReq := mood.GetMoodsResponse{}
|
||||||
|
err := dec.Decode(&happyReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error with happy")
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprintf(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug().
|
||||||
|
Interface("mood", happyReq).
|
||||||
|
Msg("mood")
|
||||||
|
user, err := web.uf.FromStr(uid, verify)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("error getting user")
|
||||||
|
w.WriteHeader(403)
|
||||||
|
fmt.Fprintf(w, "Error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := web.mood.RecordMood(happyReq, user); err != nil {
|
||||||
|
log.Error().Err(err).Msg("error saving mood")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprintf(w, "Error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "ok")
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/mood"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/user"
|
||||||
|
|
||||||
|
packr "github.com/gobuffalo/packr/v2"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/email"
|
||||||
|
"github.com/speps/go-hashids"
|
||||||
|
|
||||||
|
"code.chrissexton.org/cws/happy/db"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/stretchr/graceful"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Web struct {
|
||||||
|
addr string
|
||||||
|
assetPath string
|
||||||
|
db *db.Database
|
||||||
|
salt string
|
||||||
|
h *hashids.HashID
|
||||||
|
box *packr.Box
|
||||||
|
email *email.EMailClient
|
||||||
|
uf *user.UserFactory
|
||||||
|
mood *mood.Mood
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(addr, assetPath string, db *db.Database, salt string, h *hashids.HashID, box *packr.Box, email *email.EMailClient) *Web {
|
||||||
|
w := &Web{
|
||||||
|
addr: addr,
|
||||||
|
assetPath: assetPath,
|
||||||
|
db: db,
|
||||||
|
box: box,
|
||||||
|
email: email,
|
||||||
|
uf: user.NewFactory(db, salt, h),
|
||||||
|
mood: mood.New(db),
|
||||||
|
}
|
||||||
|
if err := db.MakeDB(); err != nil {
|
||||||
|
log.Fatal().
|
||||||
|
Err(err).
|
||||||
|
Msg("could not create database")
|
||||||
|
}
|
||||||
|
if email != nil {
|
||||||
|
u, _ := w.uf.NewUser()
|
||||||
|
w.email.SendNewUserMail("chris@chrissexton.org", u, "http://happy.chrissexton.org")
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) routeSetup() *mux.Router {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
api := r.PathPrefix("/v1/").Subrouter()
|
||||||
|
api.HandleFunc("/moods", web.getMoods).Methods("GET")
|
||||||
|
api.HandleFunc("/moods", web.handleMood).Methods("POST")
|
||||||
|
api.HandleFunc("/user/code", web.handlerRegisterCode).Methods("GET")
|
||||||
|
api.HandleFunc("/user/info", web.checkUser).Methods("GET")
|
||||||
|
r.PathPrefix("/").HandlerFunc(web.indexHandler("/index.html"))
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) Serve() {
|
||||||
|
middle := web.routeSetup()
|
||||||
|
log.Info().Str("addr", "http://"+web.addr).Msg("serving HTTP")
|
||||||
|
graceful.Run(web.addr, 10*time.Second, middle)
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Develop = false
|
||||||
|
|
||||||
|
type RegisterResponse struct {
|
||||||
|
ID string
|
||||||
|
DateCreated int64
|
||||||
|
Verification string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) handlerRegisterCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
uid, err := web.uf.NewUser()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
log.Error().Err(err).Msg("error from NewUserID")
|
||||||
|
fmt.Fprintf(w, "Error registering user: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
log.Error().Err(err).Msg("error converting date")
|
||||||
|
fmt.Fprintf(w, "Error registering user: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := RegisterResponse{
|
||||||
|
ID: uid.String(),
|
||||||
|
DateCreated: uid.DateCreated.Int64,
|
||||||
|
}
|
||||||
|
if Develop {
|
||||||
|
resp.Verification, _ = web.h.EncodeInt64([]int64{uid.Verification})
|
||||||
|
}
|
||||||
|
out, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
log.Error().Err(err).Msg("error from json.Marshal")
|
||||||
|
fmt.Fprintf(w, "Error registering user: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (web *Web) checkUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
uid := r.Header.Get("X-user-id")
|
||||||
|
verify := r.Header.Get("X-user-verification")
|
||||||
|
log.Debug().
|
||||||
|
Str("uid", uid).
|
||||||
|
Str("verify", verify).
|
||||||
|
Msg("checkUser")
|
||||||
|
user, err := web.uf.FromStr(uid, verify)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("user not known")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
fmt.Fprint(w, "User not known")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
j, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
log.Error().Err(err).Msg("could not marshal user")
|
||||||
|
fmt.Fprintf(w, "%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(j)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (web *Web) indexHandler(entryPoint string) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p := path.Clean(r.URL.Path)
|
||||||
|
|
||||||
|
if web.box.Has(p) && !web.box.HasDir(p) {
|
||||||
|
f, err := web.box.Find(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error finding file")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
w.Write(f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if web.box.HasDir(p) && web.box.Has(path.Join(p, "index.html")) {
|
||||||
|
f, err := web.box.Find(path.Join(p, "index.html"))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error finding file")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
w.Write(f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, err := web.box.Find(p); err != nil {
|
||||||
|
w.Write(f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
Loading…
Reference in New Issue