+
+ {{userInfo.ID}} ● Log Out
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 63eb05f..8df337f 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,8 +1,19 @@
import Vue from 'vue'
-import App from './App.vue'
+Vue.config.productionTip = false;
-Vue.config.productionTip = false
+import VueCookies from 'vue-cookies'
+Vue.use(VueCookies);
+VueCookies.config('30d');
+
+import BootstrapVue from 'bootstrap-vue'
+Vue.use(BootstrapVue)
+import 'bootstrap/dist/css/bootstrap.css'
+import 'bootstrap-vue/dist/bootstrap-vue.css'
+
+import store from './store.js';
+import App from './App.vue';
new Vue({
render: h => h(App),
-}).$mount('#app')
+ store,
+}).$mount('#app');
diff --git a/frontend/src/store.js b/frontend/src/store.js
new file mode 100644
index 0000000..aedb119
--- /dev/null
+++ b/frontend/src/store.js
@@ -0,0 +1,47 @@
+import Vue from 'vue';
+import Vuex from "vuex";
+
+import api from './api.js'
+
+Vue.use(Vuex);
+
+const store = new Vuex.Store({
+ state: {
+ err: null,
+ userInfo: null,
+ moods: [],
+ },
+ actions: {
+ getMoods({commit, state}) {
+ return api.getMoods(state.userInfo)
+ .then(resp => {
+ commit('setMoods', resp.data)
+ })
+ },
+ getNewUser({commit}) {
+ return api.getNewUser()
+ .then(resp => {
+ commit('setUser', resp.data);
+ })
+ },
+ setMood({state}, mood) {
+ return api.setMood(state.userInfo, mood)
+ },
+ },
+ mutations: {
+ setUser(state, userInfo) {
+ state.userInfo = userInfo;
+ },
+ setMoods(state, moods) {
+ state.moods = moods;
+ },
+ clearError(state) {
+ state.err = null
+ },
+ setError(state, err) {
+ state.err = err
+ }
+ }
+});
+
+export default store
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 05ae48a..790be44 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -759,6 +759,15 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@nuxt/opencollective@^0.3.0":
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.3.0.tgz#11d8944dcf2d526e31660bb69570be03f8fb72b7"
+ integrity sha512-Vf09BxCdj1iT2IRqVwX5snaY2WCTkvM0O4cWWSO1ThCFuc4if0Q/nNwAgCxRU0FeYHJ7DdyMUNSdswCLKlVqeg==
+ dependencies:
+ chalk "^2.4.2"
+ consola "^2.10.1"
+ node-fetch "^2.6.0"
+
"@soda/friendly-errors-webpack-plugin@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.1.tgz#706f64bcb4a8b9642b48ae3ace444c70334d615d"
@@ -1450,6 +1459,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+axios@^0.19.0:
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
+ integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
+ dependencies:
+ follow-redirects "1.5.10"
+ is-buffer "^2.0.2"
+
babel-eslint@^10.0.1:
version "10.0.3"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
@@ -1593,6 +1610,22 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
+bootstrap-vue@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.4.tgz#9bf99754a22acece72e02adda3c7965b818ae2bc"
+ integrity sha512-/5WXa3ir5uajcs7ze7jz7QXpYuJGWHjvJ8biMq0+e0IIIxw2jSdh4LsiFKD7C7qtgKre28hhXOfx79RmZX4wcQ==
+ dependencies:
+ "@nuxt/opencollective" "^0.3.0"
+ bootstrap ">=4.3.1 <5.0.0"
+ popper.js "^1.15.0"
+ portal-vue "^2.1.6"
+ vue-functional-data-merge "^3.1.0"
+
+"bootstrap@>=4.3.1 <5.0.0", bootstrap@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
+ integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -2175,6 +2208,11 @@ connect-history-api-fallback@^1.6.0:
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
+consola@^2.10.1:
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-2.10.1.tgz#4693edba714677c878d520e4c7e4f69306b4b927"
+ integrity sha512-4sxpH6SGFYLADfUip4vuY65f/gEogrzJoniVhNUYkJHtng0l8ZjnDCqxxrSVRHOHwKxsy8Vm5ONZh1wOR3/l/w==
+
console-browserify@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
@@ -2574,6 +2612,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
+debug@=3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -3481,6 +3526,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
follow-redirects@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.9.0.tgz#8d5bcdc65b7108fe1508649c79c12d732dcedb4f"
@@ -4241,6 +4293,11 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+is-buffer@^2.0.2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
+ integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
+
is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
@@ -5195,6 +5252,11 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
+node-fetch@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
+ integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+
node-forge@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
@@ -5863,6 +5925,16 @@ pkg-up@^2.0.0:
dependencies:
find-up "^2.1.0"
+popper.js@^1.15.0:
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.0.tgz#2e1816bcbbaa518ea6c2e15a466f4cb9c6e2fbb3"
+ integrity sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==
+
+portal-vue@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.6.tgz#a7d4790b14a79af7fd159a60ec88c30cddc6c639"
+ integrity sha512-lvCF85D4e8whd0nN32D8FqKwwkk7nYUI3Ku8UAEx4Z1reomu75dv5evRUTZNaj1EalxxWNXiNl0EHRq36fG8WA==
+
portfinder@^1.0.20, portfinder@^1.0.24:
version "1.0.25"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca"
@@ -7828,6 +7900,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
+vue-cookies@^1.5.13:
+ version "1.5.13"
+ resolved "https://registry.yarnpkg.com/vue-cookies/-/vue-cookies-1.5.13.tgz#b1ca79a2663bf9a4f36982557011cab5ee2196c0"
+ integrity sha512-8pjpXnvbNWx1Lft0t3MJnW+ylv0Wa2Tb6Ch617u/pQah3+SfXUZZdkh3EL3bSpe/Sw2Wdw3+DhycgQsKANSxXg==
+
vue-eslint-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1"
@@ -7840,6 +7917,11 @@ vue-eslint-parser@^5.0.0:
esquery "^1.0.1"
lodash "^4.17.11"
+vue-functional-data-merge@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657"
+ integrity sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==
+
vue-hot-reload-api@^2.3.0:
version "2.3.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
@@ -7882,6 +7964,11 @@ vue@^2.6.10:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
+vuex@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.1.tgz#0c264bfe30cdbccf96ab9db3177d211828a5910e"
+ integrity sha512-ER5moSbLZuNSMBFnEBVGhQ1uCBNJslH9W/Dw2W7GZN23UQA69uapP5GTT9Vm8Trc0PzBSVt6LzF3hGjmv41xcg==
+
watchpack@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
diff --git a/go.mod b/go.mod
index 2f1a47d..bff4fd7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,11 @@ module github.com/chrissexton/happy
go 1.12
require (
- github.com/carbocation/interpose v0.0.0-20161206215253-723534742ba3
- github.com/gorilla/handlers v1.4.2
github.com/gorilla/mux v1.7.3
- github.com/julienschmidt/httprouter v1.3.0
+ github.com/jmoiron/sqlx v1.2.0
+ github.com/mattn/go-sqlite3 v1.9.0
github.com/rs/zerolog v1.15.0
+ github.com/speps/go-hashids v2.0.0+incompatible
github.com/stretchr/graceful v1.2.15
+ google.golang.org/appengine v1.6.5 // indirect
)
diff --git a/go.sum b/go.sum
index 7c69276..aef80f5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,24 +1,36 @@
-github.com/carbocation/interpose v0.0.0-20161206215253-723534742ba3 h1:RtCys6GUprNaPOP04Zuo65wS10PMbSPPZNvIb9xYYLE=
-github.com/carbocation/interpose v0.0.0-20161206215253-723534742ba3/go.mod h1:4PGcghc3ZjA/uozANO8lCHo/gnHyMsm8iFYppSkVE/M=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
-github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
+github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+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/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
+github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
github.com/stretchr/graceful v1.2.15 h1:vmXbwPGfe8bI6KkgmHry/P1Pk63bM3TDcfi+5mh+VHg=
github.com/stretchr/graceful v1.2.15/go.mod h1:IxdGAOTZueMKoBr3oJIzdeg5CCCXbHXfV44sLhfAXXI=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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=
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
diff --git a/serve.go b/serve.go
index bd7cb9a..aff7541 100644
--- a/serve.go
+++ b/serve.go
@@ -1,47 +1,297 @@
package main
import (
+ "database/sql"
+ "encoding/json"
"flag"
"fmt"
+ "math/rand"
"net/http"
"os"
"path"
"time"
+ "github.com/speps/go-hashids"
+
+ _ "github.com/mattn/go-sqlite3"
+
"github.com/gorilla/mux"
+ "github.com/jmoiron/sqlx"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/graceful"
)
var (
- distPath = flag.String("dist", "frontend/dist", "path to dist files")
+ distPath = flag.String("dist", "frontend/dist", "path to dist files")
+ 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")
)
func main() {
flag.Parse()
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
zerolog.SetGlobalLevel(zerolog.DebugLevel)
- s := server{"0.0.0.0:8080", "pub"}
+ s := server{
+ addr: "0.0.0.0:8080",
+ assetPath: "pub",
+ salt: *salt,
+ }
+ 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 = s.salt
+ hd.MinLength = *minHashLen
+ s.h, _ = hashids.NewWithData(hd)
+ if err := s.makeDB(); err != nil {
+ log.Fatal().
+ Err(err).
+ Msg("could not create database")
+ }
s.serve()
}
-func logger(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- log.Info().
- Str("Path", r.URL.Path).
- Str("Method", r.Method).
- Msg("HTTP Request")
- })
-}
-
type server struct {
addr string
assetPath string
+ db *sqlx.DB
+ salt string
+ h *hashids.HashID
+}
+
+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
+ }
+ return nil
+}
+
+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}
+ 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) handleMood(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "hello")
+ 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) {
@@ -65,13 +315,15 @@ func (s *server) indexHandler(entryPoint string) func(w http.ResponseWriter, r *
func (s *server) routeSetup() *mux.Router {
r := mux.NewRouter()
api := r.PathPrefix("/v1/").Subrouter()
- api.HandleFunc("/mood", s.handleMood).Methods("POST")
+ api.HandleFunc("/moods", s.getMoods).Methods("GET")
+ api.HandleFunc("/moods", s.handleMood).Methods("POST")
+ api.HandleFunc("/register/code", s.handlerRegisterCode).Methods("GET")
r.PathPrefix("/").HandlerFunc(s.indexHandler("/index.html"))
return r
}
func (s *server) serve() {
middle := s.routeSetup()
- log.Info().Str("addr", s.addr).Msg("serving HTTP")
+ log.Info().Str("addr", "http://"+s.addr).Msg("serving HTTP")
graceful.Run(s.addr, 10*time.Second, middle)
}