Compare commits
No commits in common. "b04991dbd47d723a5320b00c193bf120d088ab5f" and "154f2a51960e6c7143b7dcc1456090a80e3aa3f6" have entirely different histories.
b04991dbd4
...
154f2a5196
|
@ -262,4 +262,3 @@ happy
|
|||
frontend/dist
|
||||
*.db
|
||||
*-packr.go
|
||||
env.make
|
||||
|
|
6
Makefile
6
Makefile
|
@ -1,5 +1,3 @@
|
|||
-include env.make
|
||||
|
||||
frontend-deps: frontend/node_modules
|
||||
cd frontend && yarn
|
||||
|
||||
|
@ -12,7 +10,7 @@ frontend: frontend-deps
|
|||
frontend-watch:
|
||||
cd frontend && yarn build --watch
|
||||
|
||||
run: *.go .EXPORT_ALL_VARIABLES
|
||||
run: *.go
|
||||
go build && ./happy -develop
|
||||
|
||||
.PHONY: run frontend frontend-watch .EXPORT_ALL_VARIABLES
|
||||
.PHONY: run frontend frontend-watch
|
||||
|
|
84
db/db.go
84
db/db.go
|
@ -1,84 +0,0 @@
|
|||
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()
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"code.chrissexton.org/cws/happy/user"
|
||||
|
||||
mail "github.com/xhit/go-simple-mail"
|
||||
)
|
||||
|
||||
var NoReply = `noreply@chrissexton.org`
|
||||
var NewUserSubject = `Welcome to Happy`
|
||||
var NewUserBody = `Welcome, {{.String}},
|
||||
|
||||
You may activate your new account with this link:
|
||||
{{.BaseURL}}/user/{{.String}}/{{.Verification}}
|
||||
|
||||
Be sure to keep this URL and use it to log in to any devices. If you should lose it, just ask for a new one via the website.
|
||||
You should always see {{.String}} at the bottom of the screen if you are logged in to this account.`
|
||||
|
||||
var newUserTpl = template.Must(template.New("NewUser").Parse(NewUserBody))
|
||||
|
||||
type EMailClient struct {
|
||||
*mail.SMTPServer
|
||||
}
|
||||
|
||||
func New(addr string, port int, user, pass string) *EMailClient {
|
||||
m := EMailClient{
|
||||
mail.NewSMTPClient(),
|
||||
}
|
||||
m.Host = addr
|
||||
m.Port = port
|
||||
m.Username = user
|
||||
m.Password = pass
|
||||
m.Encryption = mail.EncryptionTLS
|
||||
|
||||
m.KeepAlive = false
|
||||
m.ConnectTimeout = 10 * time.Second
|
||||
m.SendTimeout = 10 * time.Second
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (e *EMailClient) Send(from, to, subject, body string, isHTML bool) error {
|
||||
client, err := e.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := mail.NewMSG()
|
||||
|
||||
if isHTML {
|
||||
msg.SetBody(mail.TextHTML, body)
|
||||
} else {
|
||||
msg.SetBody(mail.TextPlain, body)
|
||||
}
|
||||
|
||||
msg.SetFrom(from).
|
||||
AddTo(to).
|
||||
SetSubject(subject)
|
||||
|
||||
err = msg.Send(client)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *EMailClient) SendNewUserMail(to string, u user.User, url string) error {
|
||||
w := bytes.NewBufferString("")
|
||||
tplArgs := struct {
|
||||
user.User
|
||||
BaseURL string
|
||||
}{u, url}
|
||||
newUserTpl.Execute(w, tplArgs)
|
||||
return e.Send(NoReply, to, NewUserSubject, w.String(), false)
|
||||
}
|
|
@ -5,7 +5,7 @@ export default {
|
|||
return axios.get("/v1/moods", {
|
||||
headers: {
|
||||
'X-user-id': userInfo.ID,
|
||||
'X-user-verification': userInfo.Verification
|
||||
'X-user-validation': userInfo.Validation
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -13,7 +13,7 @@ export default {
|
|||
return axios.post("/v1/moods", mood, {
|
||||
headers: {
|
||||
'X-user-id': userInfo.ID,
|
||||
'X-user-verification': userInfo.Verification
|
||||
'X-user-validation': userInfo.Validation
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -24,7 +24,7 @@ export default {
|
|||
return axios.get("/v1/user/info", {
|
||||
headers: {
|
||||
'X-user-id': userInfo.ID,
|
||||
'X-user-verification': userInfo.Verification
|
||||
'X-user-validation': userInfo.Validation
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ Vue.use(Vuex);
|
|||
const store = new Vuex.Store({
|
||||
state: {
|
||||
errs: [],
|
||||
userInfo: {ID: "", Verification: ""},
|
||||
userInfo: {ID: "", Validation: ""},
|
||||
moods: [],
|
||||
},
|
||||
actions: {
|
||||
|
|
13
go.mod
13
go.mod
|
@ -1,4 +1,4 @@
|
|||
module code.chrissexton.org/cws/happy
|
||||
module github.com/chrissexton/happy
|
||||
|
||||
go 1.12
|
||||
|
||||
|
@ -6,17 +6,16 @@ require (
|
|||
github.com/gobuffalo/packr/v2 v2.7.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/mattn/go-sqlite3 v1.11.0
|
||||
github.com/mattn/go-sqlite3 v1.9.0
|
||||
github.com/rogpeppe/go-internal v1.5.0 // indirect
|
||||
github.com/rs/zerolog v1.16.0
|
||||
github.com/rs/zerolog v1.15.0
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect
|
||||
github.com/speps/go-hashids v2.0.0+incompatible
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/graceful v1.2.15
|
||||
github.com/xhit/go-simple-mail v2.2.2+incompatible
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
|
||||
golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 // indirect
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c // indirect
|
||||
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 // indirect
|
||||
golang.org/x/sys v0.0.0-20191028145128-b67d8b46d239 // indirect
|
||||
golang.org/x/tools v0.0.0-20191028143239-8715e36070db // indirect
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
)
|
||||
|
|
22
go.sum
22
go.sum
|
@ -1,4 +1,3 @@
|
|||
code.chrissexton.org/cws/happy v0.0.0-20191028162954-154f2a51960e h1:z5VkC/O60AYvzRtMLCGI2f4jKDkZkAFDVjCfUJfrfug=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
|
@ -7,7 +6,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
|||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||
|
@ -35,23 +33,18 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
|
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
@ -63,8 +56,6 @@ github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/rs/zerolog v1.16.0 h1:AaELmZdcJHT8m6oZ5py4213cdFK8XGXkB3dFdAQ+P7Q=
|
||||
github.com/rs/zerolog v1.16.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
|
@ -90,11 +81,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xhit/go-simple-mail v2.2.2+incompatible h1:Hm2VGfLqiQJ/NnC8SYsrPOPyVYIlvP2kmnotP4RIV74=
|
||||
github.com/xhit/go-simple-mail v2.2.2+incompatible/go.mod h1:I8Ctg6vIJZ+Sv7k/22M6oeu/tbFumDY0uxBuuLbtU7Y=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
@ -103,14 +91,11 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhi
|
|||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow=
|
||||
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
|
@ -123,27 +108,20 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5d
|
|||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191028145128-b67d8b46d239 h1:UTx+LbSWbZFJ8IFqpAx3ns0lPXY+EBFKtDZRiyILdn0=
|
||||
golang.org/x/sys v0.0.0-20191028145128-b67d8b46d239/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE=
|
||||
golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3 h1:2AmBLzhAfXj+2HCW09VCkJtHIYgHTIPcTeYqgP7Bwt0=
|
||||
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191028143239-8715e36070db h1:xzcb0oYS6YlLAFxFJtuJ5GS6TVcVi3zQsh5Pf6LtASA=
|
||||
golang.org/x/tools v0.0.0-20191028143239-8715e36070db/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
29
mood/mood.go
29
mood/mood.go
|
@ -1,29 +0,0 @@
|
|||
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
|
||||
}
|
416
serve.go
416
serve.go
|
@ -1,61 +1,34 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"code.chrissexton.org/cws/happy/web"
|
||||
|
||||
"code.chrissexton.org/cws/happy/db"
|
||||
|
||||
packr "github.com/gobuffalo/packr/v2"
|
||||
|
||||
"code.chrissexton.org/cws/happy/email"
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
hashids "github.com/speps/go-hashids"
|
||||
"github.com/stretchr/graceful"
|
||||
)
|
||||
|
||||
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")
|
||||
minHashLen = flag.Int("minHashLen", 4, "minimum ID hash size")
|
||||
develop = flag.Bool("develop", false, "turn on develop mode")
|
||||
mailAddr = flag.String("mailAddr", "", "email server address")
|
||||
mailPort = flag.Int("mailPort", 587, "email server port")
|
||||
mailUser = flag.String("mailUser", "", "email user")
|
||||
mailPass = flag.String("mailPass", "", "email password")
|
||||
)
|
||||
|
||||
func validateMail() bool {
|
||||
log.Debug().Str("mailAddr", *mailAddr).Msg("about to look up mail")
|
||||
if val, ok := os.LookupEnv("MAIL_ADDR"); *mailAddr == "" && ok {
|
||||
*mailAddr = val
|
||||
log.Debug().Str("addr", *mailAddr).Str("val", val).Msg("set mailAddr")
|
||||
}
|
||||
if val, ok := os.LookupEnv("MAIL_PORT"); *mailPort == 0 && ok {
|
||||
*mailPort, _ = strconv.Atoi(val)
|
||||
log.Debug().Int("val", *mailPort).Msg("set mailPort")
|
||||
}
|
||||
if val, ok := os.LookupEnv("MAIL_USER"); *mailUser == "" && ok {
|
||||
*mailUser = val
|
||||
log.Debug().Str("val", *mailUser).Msg("set mailUser")
|
||||
}
|
||||
if val, ok := os.LookupEnv("MAIL_PASS"); *mailPass == "" && ok {
|
||||
*mailPass = val
|
||||
log.Debug().Str("val", *mailPass).Msg("set mailPass")
|
||||
}
|
||||
if *mailAddr != "" && *mailPort != 0 && *mailUser != "" && *mailPass != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
|
@ -64,25 +37,368 @@ func main() {
|
|||
box := packr.New("dist", "frontend/dist")
|
||||
log.Debug().Strs("dirlist", box.List()).Msg("packr made")
|
||||
|
||||
db, err := db.New(*dbPath)
|
||||
s := server{
|
||||
addr: "0.0.0.0:8080",
|
||||
assetPath: "pub",
|
||||
salt: *salt,
|
||||
box: box,
|
||||
}
|
||||
|
||||
db, err := sqlx.Connect("sqlite3", "happy.db")
|
||||
if err != nil {
|
||||
log.Fatal().
|
||||
Err(err).
|
||||
Msg("could not connect to database")
|
||||
}
|
||||
|
||||
s.db = db
|
||||
hd := hashids.NewData()
|
||||
hd.Salt = *salt
|
||||
hd.Salt = s.salt
|
||||
hd.MinLength = *minHashLen
|
||||
h, _ := hashids.NewWithData(hd)
|
||||
s.h, _ = hashids.NewWithData(hd)
|
||||
if err := s.makeDB(); err != nil {
|
||||
log.Fatal().
|
||||
Err(err).
|
||||
Msg("could not create database")
|
||||
}
|
||||
s.serve()
|
||||
}
|
||||
|
||||
var mailClient *email.EMailClient
|
||||
if validateMail() {
|
||||
log.Debug().Msg("sending mail")
|
||||
mailClient = email.New(*mailAddr, *mailPort, *mailUser, *mailPass)
|
||||
} else {
|
||||
log.Debug().Msg("mail disabled")
|
||||
type server struct {
|
||||
addr string
|
||||
assetPath string
|
||||
db *sqlx.DB
|
||||
salt string
|
||||
h *hashids.HashID
|
||||
box *packr.Box
|
||||
}
|
||||
s := web.New(*httpAddr, "pub", db, *salt, h, box, mailClient)
|
||||
s.Serve()
|
||||
|
||||
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 UserID) 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
|
||||
}
|
||||
|
||||
type UserID struct {
|
||||
db *sqlx.DB
|
||||
|
||||
ID int64
|
||||
Email sql.NullString
|
||||
Verification int64
|
||||
DateCreated sql.NullInt64 `db:"dateCreated"`
|
||||
LastLogin sql.NullInt64 `db:"lastLogin"`
|
||||
|
||||
salt int64
|
||||
str string
|
||||
}
|
||||
|
||||
func (s *server) NewUserID() (UserID, error) {
|
||||
uid := UserID{
|
||||
db: s.db,
|
||||
DateCreated: sql.NullInt64{time.Now().Unix(), true},
|
||||
LastLogin: sql.NullInt64{time.Now().Unix(), true},
|
||||
Verification: rand.Int63(),
|
||||
}
|
||||
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
|
||||
idstr, err := s.h.EncodeInt64([]int64{id})
|
||||
if err != nil {
|
||||
return uid, err
|
||||
}
|
||||
uid.str = idstr
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
func (s *server) FromStr(uid, verification string) (UserID, error) {
|
||||
id := UserID{db: s.db}
|
||||
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
|
||||
}
|
||||
|
||||
func (u UserID) String() string {
|
||||
return u.str
|
||||
}
|
||||
|
||||
type RegisterResponse struct {
|
||||
ID string
|
||||
DateCreated int64
|
||||
Validation string `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (s *server) handlerRegisterCode(w http.ResponseWriter, r *http.Request) {
|
||||
uid, err := s.NewUserID()
|
||||
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)
|
||||
}
|
||||
|
|
50
user/user.go
50
user/user.go
|
@ -1,50 +0,0 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"code.chrissexton.org/cws/happy/db"
|
||||
|
||||
"github.com/speps/go-hashids"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
db *db.Database
|
||||
|
||||
ID int64
|
||||
Email sql.NullString
|
||||
Verification int64
|
||||
DateCreated sql.NullInt64 `db:"dateCreated"`
|
||||
LastLogin sql.NullInt64 `db:"lastLogin"`
|
||||
|
||||
salt string
|
||||
str string
|
||||
}
|
||||
|
||||
func New(db *db.Database, salt string, h *hashids.HashID) User {
|
||||
u := User{
|
||||
db: db,
|
||||
salt: salt,
|
||||
|
||||
DateCreated: sql.NullInt64{time.Now().Unix(), true},
|
||||
LastLogin: sql.NullInt64{time.Now().Unix(), true},
|
||||
Verification: rand.Int63(),
|
||||
}
|
||||
u.UpdateID(h)
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *User) UpdateID(h *hashids.HashID) error {
|
||||
idstr, err := h.EncodeInt64([]int64{u.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.str = idstr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u User) String() string {
|
||||
return u.str
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
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")
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var Develop = true
|
||||
|
||||
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)
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"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)
|
||||
}
|
||||
write(w, f, p)
|
||||
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)
|
||||
}
|
||||
write(w, f, p)
|
||||
return
|
||||
}
|
||||
|
||||
if f, err := web.box.Find(p); err != nil {
|
||||
write(w, f, p)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
func write(w http.ResponseWriter, f []byte, path string) {
|
||||
ctype := mime.TypeByExtension(filepath.Ext(path))
|
||||
log.Debug().Msgf("detected type %s for %s by extension", ctype, path)
|
||||
if ctype == "" {
|
||||
ctype = http.DetectContentType(f)
|
||||
log.Debug().Msgf("detected type %s for %s by content", ctype, path)
|
||||
}
|
||||
if ctype != "" {
|
||||
w.Header().Set("Content-Type", ctype)
|
||||
}
|
||||
w.Write(f)
|
||||
}
|
Loading…
Reference in New Issue