happy: add working backend and frontend
Current version: * has user generation (but no real validation) * allows user to select a mood * handles a few error situations, but not enough * looks plain
This commit is contained in:
parent
104af726f8
commit
2e472c4993
|
@ -260,3 +260,4 @@ $RECYCLE.BIN/
|
|||
.idea
|
||||
happy
|
||||
frontend/dist
|
||||
*.db
|
||||
|
|
2
Makefile
2
Makefile
|
@ -8,6 +8,6 @@ frontend-watch:
|
|||
cd frontend && yarn build --watch
|
||||
|
||||
run: build
|
||||
./happy
|
||||
./happy -develop
|
||||
|
||||
.PHONY: run frontend frontend-watch
|
||||
|
|
|
@ -8,8 +8,13 @@
|
|||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-vue": "^2.0.4",
|
||||
"core-js": "^3.1.2",
|
||||
"vue": "^2.6.10"
|
||||
"vue": "^2.6.10",
|
||||
"vue-cookies": "^1.5.13",
|
||||
"vuex": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.0.0",
|
||||
|
|
|
@ -1,28 +1,39 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<img alt="Vue logo" src="./assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
</div>
|
||||
<div id="app">
|
||||
<div v-show="err">{{err}}</div>
|
||||
<user-info class="fixed-bottom userInfo"/>
|
||||
<Chooser/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import Chooser from './components/Chooser.vue'
|
||||
import UserInfo from "./components/UserInfo";
|
||||
import {mapState} from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
HelloWorld
|
||||
}
|
||||
}
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
Chooser,
|
||||
UserInfo
|
||||
},
|
||||
computed: mapState({
|
||||
userInfo: state => state.userInfo,
|
||||
err: state => state.err
|
||||
}),
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
.userInfo {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
getMoods(userInfo) {
|
||||
return axios.get("/v1/moods", {
|
||||
headers: {
|
||||
'X-user-id': userInfo ? userInfo.ID : null,
|
||||
'X-user-validation': userInfo ? userInfo.Validation : null
|
||||
}
|
||||
})
|
||||
},
|
||||
setMood(userInfo, mood) {
|
||||
return axios.post("/v1/moods", mood, {
|
||||
headers: {
|
||||
'X-user-id': userInfo.ID,
|
||||
'X-user-validation': userInfo.Validation
|
||||
}
|
||||
})
|
||||
},
|
||||
getNewUser() {
|
||||
return axios.get("/v1/register/code")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<b-button v-on:click="click" variant="outline-primary" class="moodButton">{{mood.Key}}</b-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ChoiceButton",
|
||||
props: {
|
||||
mood: Object
|
||||
},
|
||||
methods: {
|
||||
click: function(e) {
|
||||
this.$emit('selected', e, this.mood)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.moodButton {
|
||||
font-size: 24pt;
|
||||
margin: 1em;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div>
|
||||
<choice-button v-for="(mood, index) in moods"
|
||||
v-bind:key="mood.key"
|
||||
v-bind:index="index"
|
||||
:mood="mood"
|
||||
v-on:selected="doIt"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChoiceButton from "./ChoiceButton";
|
||||
import { mapState } from 'vuex';
|
||||
export default {
|
||||
name: "Chooser",
|
||||
components: {ChoiceButton},
|
||||
created() {
|
||||
this.$store.dispatch('getMoods');
|
||||
},
|
||||
computed: mapState({
|
||||
moods: state => state.moods
|
||||
}),
|
||||
methods: {
|
||||
doIt: function(e, mood) {
|
||||
this.$store.dispatch("setMood", mood)
|
||||
.catch(resp => {
|
||||
this.$store.commit('setError', resp.message);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
{{userInfo.ID}} ● <b-button variant="link" class="logOut" v-on:click="logOut">Log Out</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex';
|
||||
export default {
|
||||
name: "UserInfo",
|
||||
computed: mapState({
|
||||
userInfo: state => state.userInfo
|
||||
}),
|
||||
created() {
|
||||
this.newUser();
|
||||
},
|
||||
methods: {
|
||||
newUser: function () {
|
||||
let userInfo = this.$cookies.get("userInfo");
|
||||
if (!userInfo) {
|
||||
this.$store.dispatch('getNewUser')
|
||||
.then(() => {
|
||||
this.$cookies.set("userInfo", this.$store.state.userInfo);
|
||||
});
|
||||
} else {
|
||||
this.$store.commit('setUser', userInfo);
|
||||
}
|
||||
},
|
||||
logOut: function () {
|
||||
this.$cookies.remove("userInfo");
|
||||
this.$store.commit('clearError');
|
||||
this.newUser()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logOut {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -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');
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
|
|
7
go.mod
7
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
|
||||
)
|
||||
|
|
24
go.sum
24
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=
|
||||
|
|
280
serve.go
280
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue