persistant storage for vuex, logging instead of console.log, profile support

This commit is contained in:
Tobias Kurze
2019-04-05 13:44:59 +02:00
parent 5332940a5b
commit 91edb27529
9 changed files with 213 additions and 53 deletions

39
package-lock.json generated
View File

@@ -4932,6 +4932,11 @@
"is-symbol": "^1.0.2" "is-symbol": "^1.0.2"
} }
}, },
"es6-object-assign": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
},
"escape-html": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -7546,6 +7551,11 @@
"integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
"dev": true "dev": true
}, },
"js-cookie": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz",
"integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s="
},
"js-levenshtein": { "js-levenshtein": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
@@ -11259,6 +11269,11 @@
"nanoid": "^2.0.0" "nanoid": "^2.0.0"
} }
}, },
"shvl": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/shvl/-/shvl-1.3.1.tgz",
"integrity": "sha512-+rRPP46hloYUAEImJcqprUgXu+05Ikqr4h4V+w5i2zJy37nAqtkQKufs3+3S2fDq6JNRrHMIQhB/Vaex+jgAAw=="
},
"signal-exit": { "signal-exit": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -12962,11 +12977,35 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "dev": true
}, },
"vuejs-logger": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/vuejs-logger/-/vuejs-logger-1.5.3.tgz",
"integrity": "sha512-jw+AQ+IMJBz18fA4opHsqaU7P7yQNugoGywT6i3DCd1BWqg9eUx03Fr21kayqGcP4dxUwhVkkjuOyeirxLJC8g==",
"requires": {
"es6-object-assign": "1.1.0"
}
},
"vuex": { "vuex": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.0.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.0.tgz",
"integrity": "sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==" "integrity": "sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg=="
}, },
"vuex-persistedstate": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-2.5.4.tgz",
"integrity": "sha512-XYJhKIwO+ZVlTaXyxKxnplrJ88Fnvk5aDw753bxzRw5/yMKLQ6lq9CDCBex2fwZaQcLibhtgJOxGCHjy9GLSlQ==",
"requires": {
"deepmerge": "^2.1.0",
"shvl": "^1.3.0"
},
"dependencies": {
"deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
}
}
},
"vuex-typex": { "vuex-typex": {
"version": "3.1.4", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/vuex-typex/-/vuex-typex-3.1.4.tgz", "resolved": "https://registry.npmjs.org/vuex-typex/-/vuex-typex-3.1.4.tgz",

View File

@@ -12,6 +12,7 @@
"@vue/cli": "^3.4.1", "@vue/cli": "^3.4.1",
"axios": "^0.18.0", "axios": "^0.18.0",
"bootstrap-vue": "^2.0.0-rc.13", "bootstrap-vue": "^2.0.0-rc.13",
"js-cookie": "^2.2.0",
"vue": "^2.6.6", "vue": "^2.6.6",
"vue-axios": "^2.1.4", "vue-axios": "^2.1.4",
"vue-class-component": "^6.0.0", "vue-class-component": "^6.0.0",
@@ -20,7 +21,9 @@
"vue-i18n": "^8.9.0", "vue-i18n": "^8.9.0",
"vue-property-decorator": "^7.0.0", "vue-property-decorator": "^7.0.0",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vuejs-logger": "1.5.3",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"vuex-persistedstate": "^2.5.4",
"vuex-typex": "^3.1.4" "vuex-typex": "^3.1.4"
}, },
"devDependencies": { "devDependencies": {

18
src/$log.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
import Vue from 'vue';
/*
Type augmentation for log plugin for typescript
https://vuejs.org/v2/guide/typescript.html#Augmenting-Types-for-Use-with-Plugins
https://github.com/justinkames/vuejs-logger/issues/24
Tobias
*/
declare module 'vue/types/vue' {
export interface VueConstructor<V extends Vue = Vue> {
$log: {
debug(...args: any[]): void;
info(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
fatal(...args: any[]): void;
};
}
}

View File

@@ -3,7 +3,8 @@
<div id="nav"> <div id="nav">
<router-link to="/">Home</router-link> | <router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> | <router-link to="/about">About</router-link> |
<router-link to="/login">Login</router-link> <router-link to="/login">Login</router-link> |
<router-link to="/profile">Profile</router-link>
</div> </div>
<router-view/> <router-view/>
</div> </div>

View File

@@ -1,46 +1,51 @@
import Vue from 'vue';
import axios from 'axios'; import axios from 'axios';
const API_URL = 'http://localhost:5443/api'; const API_URL = 'http://localhost:5443/api';
export function fetchSurveys() { export function fetchSurveys() {
return axios.get(`${API_URL}/surveys/`) return axios.get(`${API_URL}/surveys/`);
} }
export function fetchSurvey(surveyId: string) { export function fetchSurvey(surveyId: string) {
return axios.get(`${API_URL}/surveys/${surveyId}/`) return axios.get(`${API_URL}/surveys/${surveyId}/`);
} }
export function saveSurveyResponse(surveyResponse: any) { export function saveSurveyResponse(surveyResponse: any) {
return axios.put(`${API_URL}/surveys/${surveyResponse.id}/`, surveyResponse) return axios.put(`${API_URL}/surveys/${surveyResponse.id}/`, surveyResponse);
} }
export function postNewSurvey(survey: any, jwt: any) { export function postNewSurvey(survey: any, jwt: any) {
return axios.post(`${API_URL}/surveys/`, survey, { headers: { Authorization: `Bearer ${jwt}` } }) return axios.post(`${API_URL}/surveys/`, survey, { headers: { Authorization: `Bearer ${jwt}` } });
} }
export function authenticate(userData: any) { export function authenticate(userData: any) {
return axios.post(`${API_URL}/auth/login`, userData) return axios.post(`${API_URL}/auth/login`, userData);
} }
export function register(userData: any) { export function register(userData: any) {
return axios.post(`${API_URL}/auth/register`, userData) return axios.post(`${API_URL}/auth/register`, userData);
} }
// This function probably isn't really useful here, as a user must be redirected to the actual OIDC provider. // This function probably isn't really useful here, as a user must be redirected to the actual OIDC provider.
// -> So login involves user interaction through the frontend (and not just API calls). // -> So login involves user interaction through the frontend (and not just API calls).
export function oidc_login (redirection_url: any) { export function oidc_login(redirectionUrl: any) {
return axios.get(`${API_URL}/auth/oidc`, redirection_url) return axios.get(`${API_URL}/auth/oidc`, redirectionUrl);
} }
export function getFreshToken (refresh_token: any) { export function getFreshToken(refreshToken: any) {
return axios.get(`${API_URL}/auth/fresh`, refresh_token) return axios.get(`${API_URL}/auth/fresh`, refreshToken);
} }
export function getProviders() { export function getProviders() {
return axios.get(`${API_URL}/auth/providers`) return axios.get(`${API_URL}/auth/providers`);
} }
export function fetchUsers(jwt: any) { export function fetchUsers(jwt: any) {
console.log("JWT: " + jwt); return axios.get(`${API_URL}/v1/user`, { headers: { Authorization: `Bearer ${jwt}` } });
return axios.get(`${API_URL}/v1/user`, { headers: { Authorization: `Bearer ${jwt}` } }) }
export function fetchProfile(jwt: any) {
Vue.$log.debug("JWT: "+ jwt);
return axios.get(`${API_URL}/v1/user/profile`, { headers: { Authorization: `Bearer ${jwt}` } });
} }

View File

@@ -0,0 +1,58 @@
<!-- components/Profile.vue -->
<template>
<div>
<section class="hero is-primary">
<div class="hero-body">
<div class="container has-text-centered">
<h2 class="title">Profile</h2>
<p>{{profile.id}}</p>
<p class="subtitle error-msg">{{ errorMsg }}</p>
</div>
</div>
</section>
</div>
</template>
<script>
import { EventBus } from '@/utils'
export default {
data () {
return {
email: '',
errorMsg: '',
};
},
methods: {
authenticate () {
this.$store.dispatch('login', { email: this.email, password: this.password })
.then(() => this.$router.push('/'));
},
},
mounted () {
this.$log.debug("Profile: mounting...");
EventBus.$on('failedLoadingProfile', (msg) => {
this.errorMsg = msg;
});
this.$store.dispatch('loadProfile');
},
beforeDestroy () {
EventBus.$off('failedLoadingProfile');
},
computed: {
profile () {
return this.$store.state.profile;
},
},
}
</script>
<style lang="scss">
.error-msg {
color: red;
font-weight: bold;
}
</style>

View File

@@ -4,16 +4,31 @@ import VueAxios from 'vue-axios';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
import store from './store'; import store from './store';
import VueCookies from 'vue-cookies' import VueCookies from 'vue-cookies';
import VueLogger from 'vuejs-logger';
import i18n from '@/plugins/i18n'; import i18n from '@/plugins/i18n';
// @ts-ignore // @ts-ignore
import FlagIcon from 'vue-flag-icon'; import FlagIcon from 'vue-flag-icon';
// following is to avoid missing type definitions // following is to avoid missing type definitions
// const FlagIcon = require('vue-flag-icon'); // const FlagIcon = require('vue-flag-icon');
const isProduction = process.env.NODE_ENV === 'production';
const options = {
isEnabled: true,
// logLevel : isProduction ? 'error' : 'debug',
logLevel : 'debug',
stringifyArguments : false,
showLogLevel : true,
showMethodName : true,
separator: '|',
showConsoleColors: true
};
Vue.use(VueLogger, options);
Vue.use(VueAxios, axios); Vue.use(VueAxios, axios);
Vue.use(FlagIcon); Vue.use(FlagIcon);
Vue.use(VueCookies) Vue.use(VueCookies);
// setup fake backend // setup fake backend
// import { configureFakeBackend } from './helpers'; // import { configureFakeBackend } from './helpers';

View File

@@ -6,6 +6,7 @@ import NotFound from './views/NotFound.vue';
import Survey from '@/components/Survey.vue'; import Survey from '@/components/Survey.vue';
import NewSurvey from '@/components/NewSurvey.vue'; import NewSurvey from '@/components/NewSurvey.vue';
import Login from '@/components/Login.vue'; import Login from '@/components/Login.vue';
import Profile from '@/components/Profile.vue';
import store from '@/store'; import store from '@/store';
Vue.use(Router); Vue.use(Router);
@@ -22,6 +23,7 @@ export const router = new Router({
}, },
{ {
path: '/login', path: '/login',
name: 'Login',
component: Login, component: Login,
}, },
{ {
@@ -47,9 +49,9 @@ export const router = new Router({
} }
}, },
}, { }, {
path: '/login', path: '/profile',
name: 'Login', name: 'Profile',
component: Login, component: Profile,
}, },
{ {
path: '*', path: '*',

View File

@@ -1,6 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate'
// imports of AJAX functions will go here // imports of AJAX functions will go here
import { import {
@@ -11,7 +11,7 @@ import {
postNewSurvey, postNewSurvey,
authenticate, authenticate,
register, register,
oidc_login, fetchUsers, getFreshToken oidc_login, fetchUsers, getFreshToken, fetchProfile,
} from '@/api'; } from '@/api';
import { isValidJwt, EventBus } from '@/utils'; import { isValidJwt, EventBus } from '@/utils';
@@ -22,7 +22,7 @@ const state = {
surveys: [], surveys: [],
loginProviders: [], loginProviders: [],
currentSurvey: {}, currentSurvey: {},
user: {}, profile: {},
users: [], users: [],
access_token: '', access_token: '',
refresh_token: '', refresh_token: '',
@@ -51,38 +51,51 @@ const actions = {
loadUsers(context: any) { loadUsers(context: any) {
return fetchUsers(context.state.access_token) return fetchUsers(context.state.access_token)
.then((response) => { .then((response) => {
console.log(response); Vue.$log.debug(response);
console.log(response.data); Vue.$log.debug(response.data);
context.commit('setUsers', { users: response.data }); context.commit('setUsers', { users: response.data });
EventBus.$emit('usersLoaded', response.data); EventBus.$emit('usersLoaded', response.data);
}) })
.catch((error) => { .catch((error) => {
console.log('Error loading users!', error); Vue.$log.warn('Error loading users!', error);
EventBus.$emit('failedLoadingUsers', error); EventBus.$emit('failedLoadingUsers', error);
}); });
}, },
loadProfile(context: any) {
return fetchProfile(context.state.access_token)
.then((response) => {
Vue.$log.debug(response);
Vue.$log.debug(response.data);
context.commit('setProfile', { profile: response.data });
EventBus.$emit('profileLoaded', response.data);
})
.catch((error) => {
Vue.$log.warn('Error loading profile!', error);
EventBus.$emit('failedLoadingProfile', error);
});
},
loadLoginProviders(context: any) { loadLoginProviders(context: any) {
return getProviders() return getProviders()
.then((response) => { .then((response) => {
context.commit('setLoginProviderData', {providers: response.data}); context.commit('setLoginProviderData', {providers: response.data});
EventBus.$emit('loginProvidersLoaded', response.data); EventBus.$emit('loginProvidersLoaded', response.data);
}) });
}, },
login(context: any, userData: any) { login(context: any, userData: any) {
context.commit('setUserData', { userData }); context.commit('setUserData', { userData });
return authenticate(userData) return authenticate(userData)
.then((response) => context.commit('setJwtToken', { tokens: response.data })) .then((response) => context.commit('setJwtToken', { tokens: response.data }))
.catch((error) => { .catch((error) => {
console.log('Error Authenticating: ', error); Vue.$log.warn('Error Authenticating: ', error);
EventBus.$emit('failedAuthentication', error); EventBus.$emit('failedAuthentication', error);
}); });
}, },
oidc_login(context: any, redirection_url: any) { oidc_login(context: any, redirectionUrl: any) {
// context.commit('setUserData', { userData }); // context.commit('setUserData', { userData });
return oidc_login(redirection_url) return oidc_login(redirectionUrl)
.then((response) => context.commit('setJwtToken', { tokens: response.data })) .then((response) => context.commit('setJwtToken', { tokens: response.data }))
.catch((error) => { .catch((error) => {
console.log('Error Authenticating: ', error); Vue.$log.warn('Error Authenticating: ', error);
EventBus.$emit('failedAuthentication', error); EventBus.$emit('failedAuthentication', error);
}); });
}, },
@@ -91,7 +104,7 @@ const actions = {
return getFreshToken(context.state.refresh_token) return getFreshToken(context.state.refresh_token)
.then((response) => context.commit('setTokens', { tokens: response.data })) .then((response) => context.commit('setTokens', { tokens: response.data }))
.catch((error) => { .catch((error) => {
console.log('Error Refreshing token: ', error); Vue.$log.warn('Error Refreshing token: ', error);
EventBus.$emit('failedRefreshingToken', error); EventBus.$emit('failedRefreshingToken', error);
}); });
}, },
@@ -104,7 +117,7 @@ const actions = {
return register(userData) return register(userData)
.then(context.dispatch('login', userData)) .then(context.dispatch('login', userData))
.catch((error) => { .catch((error) => {
console.log('Error Registering: ', error); Vue.$log.warn('Error Registering: ', error);
EventBus.$emit('failedRegistering: ', error); EventBus.$emit('failedRegistering: ', error);
}); });
}, },
@@ -139,32 +152,37 @@ const mutations = {
} }
}, },
setLoginProviderData(sState: any, payload: any) { setLoginProviderData(sState: any, payload: any) {
console.log("got loginProviders = ", payload); Vue.$log.debug('got loginProviders = ', payload);
sState.loginProviders = payload.providers; sState.loginProviders = payload.providers;
}, },
// probably old ...
setUserData(sState: any, payload: any) { setUserData(sState: any, payload: any) {
console.log('setUserData payload = ', payload); Vue.$log.debug('setUserData payload = ', payload);
sState.userData = payload.userData; sState.userData = payload.userData;
}, },
setProfile(sState: any, payload: any) {
Vue.$log.debug('setProfile payload = ', payload);
sState.profile = payload.profile;
},
setJwtToken(sState: any, payload: any) { setJwtToken(sState: any, payload: any) {
console.log('setJwtToken payload = ', payload); Vue.$log.debug('setJwtToken payload = ', payload);
localStorage.tokens = payload.tokens; localStorage.tokens = payload.tokens;
sState.jwt = payload.tokens.access_token; sState.jwt = payload.tokens.access_token;
sState.jwt_refresh_token = payload.tokens.refresh_token; sState.jwt_refresh_token = payload.tokens.refresh_token;
}, },
setTokens(sState: any, payload: any) { setTokens(sState: any, payload: any) {
console.log('setTokens payload = ', payload); Vue.$log.debug('setTokens payload = ', payload);
sState.access_token = payload.tokens.access_token; sState.access_token = payload.tokens.access_token;
sState.refresh_token = payload.tokens.refresh_token; sState.refresh_token = payload.tokens.refresh_token;
console.log(sState.access_token); Vue.$log.debug(sState.access_token);
console.log(sState.access_token); Vue.$log.debug(sState.access_token);
} },
}; };
const getters = { const getters = {
// reusable data accessors // reusable data accessors
isAuthenticated(sState: any) { isAuthenticated(sState: any) {
return isValidJwt(sState.jwt.token); return isValidJwt(sState.access_token);
}, },
getLoginProviders(sState: any) { getLoginProviders(sState: any) {
return sState.loginProviders; return sState.loginProviders;
@@ -176,6 +194,7 @@ const store = new Vuex.Store({
actions, actions,
mutations, mutations,
getters, getters,
plugins: [createPersistedState()],
}); });
export default store; export default store;