Implemented frontend
This commit is contained in:
parent
773ee7db08
commit
cc426c4c0f
103
frontend/package-lock.json
generated
103
frontend/package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "notificationlatch",
|
"name": "notificationlatch-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
@ -1151,6 +1151,74 @@
|
|||||||
"postcss": "^7.0.0"
|
"postcss": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@intlify/core-base": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-q1W2j81xbHyfKrNcca/CeJyf0Bcx4u9UDu05l7AaiJbqOseTme2o2I3wp1hDDCtmC7k7HgX0sAygyHNJH9swuQ==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/devtools-if": "9.1.7",
|
||||||
|
"@intlify/message-compiler": "9.1.7",
|
||||||
|
"@intlify/message-resolver": "9.1.7",
|
||||||
|
"@intlify/runtime": "9.1.7",
|
||||||
|
"@intlify/shared": "9.1.7",
|
||||||
|
"@intlify/vue-devtools": "9.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/devtools-if": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-/DcN5FUySSkQhDqx5y1RvxfuCXO3Ot/dUEIOs472qbM7Hyb2qif+eXCnwHBzlI4+wEfQVT6L0PiM1a7Er/ro9g==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/shared": "9.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/message-compiler": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-JZNkAhr3O7tnbdbRBcpYfqr/Ai26WTzX0K/lV8Y1KVdOIj/dGiamaffdWUdFiDXUnbJRNbPiOaKxy7Pwip3KxQ==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/message-resolver": "9.1.7",
|
||||||
|
"@intlify/shared": "9.1.7",
|
||||||
|
"source-map": "0.6.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/message-resolver": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-WTK+OaXJYjyquLGhuCyDvU2WHkG+kXzXeHagmVFHn+s118Jf2143zzkLLUrapP5CtZ/csuyjmYg7b3xQRQAmvw=="
|
||||||
|
},
|
||||||
|
"@intlify/runtime": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-QURPSlzhOVnRwS2XMGpCDsDkP42kfVBh94aAORxh/gVGzdgJip2vagrIFij/J69aEqdB476WJkMhVjP8VSHmiA==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/message-compiler": "9.1.7",
|
||||||
|
"@intlify/message-resolver": "9.1.7",
|
||||||
|
"@intlify/shared": "9.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@intlify/shared": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-zt0zlUdalumvT9AjQNxPXA36UgOndUyvBMplh8uRZU0fhWHAwhnJTcf0NaG9Qvr8I1n3HPSs96+kLb/YdwTavQ=="
|
||||||
|
},
|
||||||
|
"@intlify/vue-devtools": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-DI5Wc0aOiohtBUGUkKAcryCWbbuaO4/PK4Pa/LaNCsFNxbtgR5qkIDmhBv9xVPYGTUhySXxaDDAMvOpBjhPJjw==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/message-resolver": "9.1.7",
|
||||||
|
"@intlify/runtime": "9.1.7",
|
||||||
|
"@intlify/shared": "9.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@mrmlnc/readdir-enhanced": {
|
"@mrmlnc/readdir-enhanced": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
|
||||||
@ -1279,6 +1347,12 @@
|
|||||||
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
|
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/luxon": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-L7iL3FitRSeuz8fbeLtql7qU6inHVtwEDWI1+vBXgyp0J2tmxOD7TgMBiEQjII/Y/TPcwrKasXb1BPuiCXRgxg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/mime": {
|
"@types/mime": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||||
@ -2465,6 +2539,14 @@
|
|||||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
|
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
|
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-code-frame": {
|
"babel-code-frame": {
|
||||||
"version": "6.26.0",
|
"version": "6.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||||
@ -5369,8 +5451,7 @@
|
|||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
|
||||||
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==",
|
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -7074,6 +7155,11 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"luxon": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg=="
|
||||||
|
},
|
||||||
"magic-string": {
|
"magic-string": {
|
||||||
"version": "0.25.7",
|
"version": "0.25.7",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||||
@ -11087,6 +11173,17 @@
|
|||||||
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
|
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"vue-i18n": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-ujuuDanoHqtEd4GejWrbG/fXE9nrP51ElsEGxp0WBHfv+/ki0/wyUqkO+4fLikki2obGtXdviTPH0VNpas5K6g==",
|
||||||
|
"requires": {
|
||||||
|
"@intlify/core-base": "9.1.7",
|
||||||
|
"@intlify/shared": "9.1.7",
|
||||||
|
"@intlify/vue-devtools": "9.1.7",
|
||||||
|
"@vue/devtools-api": "^6.0.0-beta.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vue-loader": {
|
"vue-loader": {
|
||||||
"version": "15.9.8",
|
"version": "15.9.8",
|
||||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
|
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
|
||||||
|
@ -7,11 +7,15 @@
|
|||||||
"build": "vue-cli-service build"
|
"build": "vue-cli-service build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"luxon": "^2.0.2",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
|
"vue-i18n": "^9.1.7",
|
||||||
"vue-router": "^4.0.0-0"
|
"vue-router": "^4.0.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/luxon": "^2.0.0",
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
"@vue/cli-plugin-router": "~4.5.0",
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title>Notifications</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but this site doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
|
@ -1,30 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="nav">
|
|
||||||
<router-link to="/">Home</router-link> |
|
|
||||||
<router-link to="/about">About</router-link>
|
|
||||||
</div>
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#app {
|
body
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
{
|
||||||
|
background: #001133;
|
||||||
|
color: #cccccc;
|
||||||
|
font-family: 'Verdana', 'Arial', sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav {
|
|
||||||
padding: 30px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #2c3e50;
|
|
||||||
|
|
||||||
&.router-link-exact-active {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
@ -1,61 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="hello">
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br>
|
|
||||||
check out the
|
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
|
||||||
</p>
|
|
||||||
<h3>Installed CLI Plugins</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Essential Links</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
|
||||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
|
||||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
|
||||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
|
||||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Ecosystem</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'HelloWorld',
|
|
||||||
props: {
|
|
||||||
msg: String,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped lang="scss">
|
|
||||||
h3 {
|
|
||||||
margin: 40px 0 0;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
24
frontend/src/locale/en.ts
Normal file
24
frontend/src/locale/en.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export default {
|
||||||
|
notification: {
|
||||||
|
loading: 'Loading, please wait...',
|
||||||
|
tokenInvalid: 'Invalid token',
|
||||||
|
listHeader: 'Disabled notifications',
|
||||||
|
|
||||||
|
latchTime: 'Sent on {latchTime}',
|
||||||
|
latched: 'This notification will not be sent again until it is re-enabled.',
|
||||||
|
enableNotification: 'Enable',
|
||||||
|
|
||||||
|
reminders: 'If enabled, a reminder will be sent every {interval}. Use the buttons below to disable or enable reminders. This will apply only to this notification.',
|
||||||
|
remindersDisabled: 'No reminders',
|
||||||
|
remindersEnabled: 'Send reminders'
|
||||||
|
},
|
||||||
|
|
||||||
|
duration: {
|
||||||
|
glue: ', ',
|
||||||
|
lastGlue: ' and ',
|
||||||
|
days: '{count} day | {count} days',
|
||||||
|
hours: '{count} hour | {count} hours',
|
||||||
|
minutes: '{count} minute | {count} minutes',
|
||||||
|
seconds: '{count} second | {count} seconds'
|
||||||
|
}
|
||||||
|
}
|
7
frontend/src/locale/index.ts
Normal file
7
frontend/src/locale/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import en from './en';
|
||||||
|
import nl from './nl';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
en,
|
||||||
|
nl
|
||||||
|
};
|
24
frontend/src/locale/nl.ts
Normal file
24
frontend/src/locale/nl.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export default {
|
||||||
|
notification: {
|
||||||
|
loading: 'Bezig met laden, een ogenblik geduld a.u.b....',
|
||||||
|
tokenInvalid: 'Ongeldig token',
|
||||||
|
listHeader: 'Uitgeschakelde meldingen',
|
||||||
|
|
||||||
|
latchTime: 'Verzonden op {latchTime}',
|
||||||
|
latched: 'Deze notificatie wordt niet meer verzonden totdat deze weer wordt ingeschakeld.',
|
||||||
|
enableNotification: 'Inschakelen',
|
||||||
|
|
||||||
|
reminders: 'Indien ingeschakeld wordt een herinnering elke {interval} gestuurd. Gebruik de knoppen hieronder om herinneringen uit of in te schakelen. Dit geldt alleen voor deze notificatie.',
|
||||||
|
remindersDisabled: 'Geen herinneringen',
|
||||||
|
remindersEnabled: 'Herinneringen sturen'
|
||||||
|
},
|
||||||
|
|
||||||
|
duration: {
|
||||||
|
glue: ', ',
|
||||||
|
lastGlue: ' en ',
|
||||||
|
days: '{count} dag | {count} dagen',
|
||||||
|
hours: '{count} uur | {count} uren',
|
||||||
|
minutes: '{count} minuut | {count} minuten',
|
||||||
|
seconds: '{count} seconde | {count} seconden'
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,17 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue'
|
import { createI18n } from 'vue-i18n';
|
||||||
import router from './router'
|
import App from './App.vue';
|
||||||
|
import router from './router';
|
||||||
|
import messages from './locale';
|
||||||
|
|
||||||
createApp(App).use(router).mount('#app')
|
const i18n = createI18n({
|
||||||
|
locale: navigator.language.substr(0, 2),
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
createApp(App)
|
||||||
|
.use(router)
|
||||||
|
.use(i18n)
|
||||||
|
.mount('#app')
|
||||||
|
28
frontend/src/model/notifications.ts
Normal file
28
frontend/src/model/notifications.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Duration } from 'luxon';
|
||||||
|
|
||||||
|
|
||||||
|
export interface ILatchedNotifications
|
||||||
|
{
|
||||||
|
reminders: INotificationReminders;
|
||||||
|
notifications: Array<INotification>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface INotificationReminders
|
||||||
|
{
|
||||||
|
enabled: boolean;
|
||||||
|
interval: Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface INotification
|
||||||
|
{
|
||||||
|
token: string;
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
latched: boolean;
|
||||||
|
latchTime: number;
|
||||||
|
resetTime?: number;
|
||||||
|
reminders: boolean;
|
||||||
|
remindTime?: number;
|
||||||
|
}
|
@ -1,25 +1,25 @@
|
|||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import Home from '../views/Home.vue'
|
import Default from '../views/Default.vue';
|
||||||
|
import Notification from '../views/Notification.vue';
|
||||||
|
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Default',
|
||||||
component: Home
|
component: Default
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/n/:token',
|
||||||
name: 'About',
|
name: 'Notification',
|
||||||
// route level code-splitting
|
component: Notification,
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
props: true
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
|
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes
|
routes
|
||||||
})
|
});
|
||||||
|
|
||||||
export default router
|
export default router;
|
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
5
frontend/src/views/Default.vue
Normal file
5
frontend/src/views/Default.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<p class="default">
|
||||||
|
Nothing to see here, move along.
|
||||||
|
</p>
|
||||||
|
</template>
|
@ -1,18 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="home">
|
|
||||||
<img alt="Vue logo" src="../assets/logo.png">
|
|
||||||
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Home',
|
|
||||||
components: {
|
|
||||||
HelloWorld,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
342
frontend/src/views/Notification.vue
Normal file
342
frontend/src/views/Notification.vue
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p class="loading" v-if="tokenValid === null">
|
||||||
|
{{ $t('notification.loading') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="invalid" v-if="tokenValid === false">
|
||||||
|
{{ $t('notification.tokenInvalid') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="notifications" v-if="tokenValid === true">
|
||||||
|
<div class="header">{{ $t('notification.listHeader') }}</div>
|
||||||
|
|
||||||
|
<div class="list">
|
||||||
|
<div class="notification" v-for="notification in orderedNotifications" :key="notification.id">
|
||||||
|
<div class="title">{{ notification.title }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="info">
|
||||||
|
<p class="latchTime">{{ $t('notification.latchTime', { latchTime: formatDateTime(notification.latchTime) }) }}</p>
|
||||||
|
<p class="latched">{{ $t('notification.latched') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a href="#" @click.prevent="enableNotification(notification)" class="enable" :class="{ active: !notification.enabling }">{{ $t('notification.enableNotification') }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="reminders" v-if="reminders.enabled">
|
||||||
|
<p class="interval">{{ $t('notification.reminders', { interval: formattedReminderInterval })}}</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a href="#" @click.prevent="enableReminders(notification)" class="reminders-enabled" :class="{ active: !notification.updatingReminders && notification.reminders }">{{ $t('notification.remindersEnabled') }}</a>
|
||||||
|
<a href="#" @click.prevent="disableReminders(notification)" class="reminders-disabled" :class="{ active: !notification.updatingReminders && !notification.reminders }">{{ $t('notification.remindersDisabled') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { DateTime, Duration } from 'luxon';
|
||||||
|
import { ILatchedNotifications, INotificationReminders, INotification } from '../model/notifications';
|
||||||
|
|
||||||
|
|
||||||
|
interface INotificationViewModel extends INotification
|
||||||
|
{
|
||||||
|
enabling: boolean;
|
||||||
|
updatingReminders: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: [
|
||||||
|
'token'
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
|
data()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
tokenValid: null as null | boolean,
|
||||||
|
reminders: null as null | INotificationReminders,
|
||||||
|
notifications: null as null | Array<INotificationViewModel>
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
mounted()
|
||||||
|
{
|
||||||
|
this.refreshToken();
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
token()
|
||||||
|
{
|
||||||
|
this.refreshToken();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
formattedReminderInterval(): string
|
||||||
|
{
|
||||||
|
if (this.reminders === null || !this.reminders.enabled)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
const duration = Duration.fromObject(this.reminders.interval);
|
||||||
|
const interval = duration.shiftTo('days', 'hours', 'minutes', 'seconds');
|
||||||
|
const units = [];
|
||||||
|
|
||||||
|
if (interval.days)
|
||||||
|
units.push(this.$tc('duration.days', interval.days));
|
||||||
|
|
||||||
|
if (interval.hours)
|
||||||
|
units.push(this.$tc('duration.hours', interval.hours));
|
||||||
|
|
||||||
|
if (interval.minutes)
|
||||||
|
units.push(this.$tc('duration.minutes', interval.minutes));
|
||||||
|
|
||||||
|
if (interval.seconds)
|
||||||
|
units.push(this.$tc('duration.seconds', interval.seconds));
|
||||||
|
|
||||||
|
if (units.length == 0)
|
||||||
|
return '<error>';
|
||||||
|
|
||||||
|
if (units.length == 1)
|
||||||
|
return units[0];
|
||||||
|
|
||||||
|
const lastUnit = units.pop();
|
||||||
|
return units.join(this.$t('duration.glue')) + this.$t('duration.lastGlue') + lastUnit;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
orderedNotifications(): Array<INotificationViewModel>
|
||||||
|
{
|
||||||
|
if (this.notifications === null)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return [...this.notifications].sort((a, b) =>
|
||||||
|
{
|
||||||
|
if (a.token === this.token)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (b.token === this.token)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return a.title.localeCompare(b.title);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async refreshToken()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const response = await axios.get<ILatchedNotifications>('/api/notification/latched', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + this.token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.reminders = response.data.reminders;
|
||||||
|
this.notifications = response.data.notifications.map(notification =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
token: notification.token,
|
||||||
|
id: notification.id,
|
||||||
|
title: notification.title,
|
||||||
|
latched: notification.latched,
|
||||||
|
latchTime: notification.latchTime,
|
||||||
|
resetTime: notification.resetTime,
|
||||||
|
reminders: notification.reminders,
|
||||||
|
remindTime: notification.remindTime,
|
||||||
|
|
||||||
|
enabling: false,
|
||||||
|
updatingReminders: false
|
||||||
|
} as INotificationViewModel;
|
||||||
|
});
|
||||||
|
this.tokenValid = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
this.tokenValid = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async enableNotification(notification: INotificationViewModel)
|
||||||
|
{
|
||||||
|
if (this.notifications == null || notification.enabling)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notification.enabling = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await axios.post('/api/notification/' + notification.token + '/reset');
|
||||||
|
this.notifications = this.notifications.filter((n: INotification) => n !== notification);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
notification.enabling = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async enableReminders(notification: INotificationViewModel)
|
||||||
|
{
|
||||||
|
if (notification.updatingReminders)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notification.updatingReminders = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await axios.post('/api/notification/' + notification.token + '/reminders/enable');
|
||||||
|
notification.reminders = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
notification.updatingReminders = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async disableReminders(notification: INotificationViewModel)
|
||||||
|
{
|
||||||
|
if (notification.updatingReminders)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notification.updatingReminders = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await axios.post('/api/notification/' + notification.token + '/reminders/disable');
|
||||||
|
notification.reminders = false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
notification.updatingReminders = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
formatDateTime(unixTimestamp: number)
|
||||||
|
{
|
||||||
|
return DateTime.fromSeconds(unixTimestamp).toLocaleString(DateTime.DATETIME_FULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.notifications
|
||||||
|
{
|
||||||
|
margin: 1rem;
|
||||||
|
|
||||||
|
.header
|
||||||
|
{
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list
|
||||||
|
{
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
.notification
|
||||||
|
{
|
||||||
|
background: #fcfcfc;
|
||||||
|
border: solid 1px #2b5074;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
|
||||||
|
.title
|
||||||
|
{
|
||||||
|
background: #d4e1ee;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content
|
||||||
|
{
|
||||||
|
margin: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info
|
||||||
|
{
|
||||||
|
font-size: .75rem;
|
||||||
|
|
||||||
|
.latchTime
|
||||||
|
{
|
||||||
|
color: gray;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions
|
||||||
|
{
|
||||||
|
margin-top: .25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enable,
|
||||||
|
.reminders-disabled,
|
||||||
|
.reminders-enabled
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
padding: .5em;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: .5rem;
|
||||||
|
color: gray;
|
||||||
|
background: white;
|
||||||
|
border: solid 1px darkgray;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active
|
||||||
|
{
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&.enable,
|
||||||
|
&.reminders-enabled
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
background: #248f24;
|
||||||
|
border: solid 1px green;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.enable
|
||||||
|
{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.reminders-disabled
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
background: darkred;
|
||||||
|
border: solid 1px red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminders
|
||||||
|
{
|
||||||
|
.interval
|
||||||
|
{
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -22,6 +22,9 @@ class NotificationFacade
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.publicUrl = config.publicUrl;
|
||||||
|
this.reminders = config.reminders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ class NotificationFacade
|
|||||||
priority: priority,
|
priority: priority,
|
||||||
sound: parsedSubject.sound,
|
sound: parsedSubject.sound,
|
||||||
timestamp: this.dateTimeProvider.unixTimestamp(),
|
timestamp: this.dateTimeProvider.unixTimestamp(),
|
||||||
url: 'https://www.hierhaduwurlkunnenstaan.nl/'
|
url: new URL('/#/n/' + token, this.publicUrl).href
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +56,28 @@ class NotificationFacade
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
getLatchedNotifications(token)
|
||||||
|
{
|
||||||
|
const notifications = this.notificationRepository.getLatchedNotifications(token);
|
||||||
|
|
||||||
|
return notifications !== null
|
||||||
|
? {
|
||||||
|
reminders: {
|
||||||
|
enabled: this.reminders.enabled,
|
||||||
|
interval: this.reminders.interval
|
||||||
|
},
|
||||||
|
notifications: notifications
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async setReminders(token, enabled)
|
||||||
|
{
|
||||||
|
await this.notificationRepository.setReminders(token, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_sendNotification(notification)
|
_sendNotification(notification)
|
||||||
{
|
{
|
||||||
this.contacts.forEach(contact =>
|
this.contacts.forEach(contact =>
|
||||||
|
@ -107,6 +107,57 @@ class NotificationRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getLatchedNotifications(token)
|
||||||
|
{
|
||||||
|
this._checkInitialized();
|
||||||
|
|
||||||
|
if (!this.notifications.hasOwnProperty(token))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Object.keys(this.notifications)
|
||||||
|
.filter(notificationToken => this.notifications[notificationToken].latched)
|
||||||
|
.map(notificationToken => {
|
||||||
|
const notification = this.notifications[notificationToken];
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: notificationToken,
|
||||||
|
id: notification.id,
|
||||||
|
title: notification.title,
|
||||||
|
latched: notification.latched,
|
||||||
|
latchTime: notification.latchTime,
|
||||||
|
resetTime: notification.resetTime,
|
||||||
|
reminders: notification.reminders,
|
||||||
|
remindTime: notification.remindTime
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async setReminders(token, enabled)
|
||||||
|
{
|
||||||
|
this._checkInitialized();
|
||||||
|
|
||||||
|
if (!this.notifications.hasOwnProperty(token))
|
||||||
|
{
|
||||||
|
this.logger.info(`Notification token '${token}' does not exist, unable to change reminders setting`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = this.notifications[token];
|
||||||
|
|
||||||
|
if (notification.reminders == enabled)
|
||||||
|
{
|
||||||
|
this.logger.info(`Notification with id '${notification.id}' and token '${token}' reminders is already ${enabled}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(`Setting reminders for notification with id '${notification.id}' and token '${token}' to ${enabled}`);
|
||||||
|
notification.reminders = enabled;
|
||||||
|
|
||||||
|
await this._flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_getNotificationToken(id)
|
_getNotificationToken(id)
|
||||||
{
|
{
|
||||||
const hasher = crypto.createHmac("sha256", this.salt);
|
const hasher = crypto.createHmac("sha256", this.salt);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
class ApiRoutes
|
class ApiRoutes
|
||||||
{
|
{
|
||||||
static create = container => new this(container.logger, container.NotificationFacade, container.Config);
|
static create = container => new this(container.Logger, container.NotificationFacade, container.Config);
|
||||||
|
|
||||||
|
|
||||||
constructor(logger, notificationFacade, config)
|
constructor(logger, notificationFacade, config)
|
||||||
@ -15,8 +15,11 @@ class ApiRoutes
|
|||||||
{
|
{
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/notification', (req, res) => this._wrapAsyncHandler(req, res, this._handlePostNotification.bind(this)));
|
router.post('/notification', this._wrapAsyncHandler(this._handlePostNotification));
|
||||||
router.post('/notification/reset', (req, res) => this._wrapAsyncHandler(req, res, this._handleResetNotification.bind(this)));
|
router.get('/notification/latched', this._wrapAsyncHandler(this._handleLatchedNotifications));
|
||||||
|
router.post('/notification/:token/reset', this._wrapAsyncHandler(this._handleResetNotification));
|
||||||
|
router.post('/notification/:token/reminders/enable', this._wrapAsyncHandler(this._handleEnableReminders));
|
||||||
|
router.post('/notification/:token/reminders/disable', this._wrapAsyncHandler(this._handleDisableReminders));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@ -45,23 +48,57 @@ class ApiRoutes
|
|||||||
|
|
||||||
async _handleResetNotification(req, res)
|
async _handleResetNotification(req, res)
|
||||||
{
|
{
|
||||||
if (!req.body || !req.body.token)
|
await this.notificationFacade.resetNotification(req.params.token);
|
||||||
{
|
|
||||||
this._logRequestWarning(req, 'Missing token');
|
|
||||||
res.sendStatus(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.notificationFacade.resetNotification(req.body.token);
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async _wrapAsyncHandler(req, res, handler)
|
async _handleEnableReminders(req, res)
|
||||||
|
{
|
||||||
|
await this.notificationFacade.setReminders(req.params.token, true);
|
||||||
|
res.sendStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async _handleDisableReminders(req, res)
|
||||||
|
{
|
||||||
|
await this.notificationFacade.setReminders(req.params.token, false);
|
||||||
|
res.sendStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async _handleLatchedNotifications(req, res)
|
||||||
|
{
|
||||||
|
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer '))
|
||||||
|
{
|
||||||
|
this._logRequestWarning(req, 'Missing or invalid authorization header');
|
||||||
|
res.sendStatus(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = req.headers.authorization.substr(7);
|
||||||
|
const notifications = this.notificationFacade.getLatchedNotifications(token);
|
||||||
|
|
||||||
|
if (notifications == null)
|
||||||
|
{
|
||||||
|
this._logRequestWarning(req, `Invalid token: ${token}`);
|
||||||
|
res.sendStatus(401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(JSON.stringify(notifications));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_wrapAsyncHandler(handler)
|
||||||
|
{
|
||||||
|
const boundHandler = handler.bind(this);
|
||||||
|
|
||||||
|
return async (req, res) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await handler(req, res);
|
await boundHandler(req, res);
|
||||||
}
|
}
|
||||||
catch (err)
|
catch (err)
|
||||||
{
|
{
|
||||||
@ -70,6 +107,7 @@ class ApiRoutes
|
|||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_logRequestWarning(req, message)
|
_logRequestWarning(req, message)
|
||||||
|
Loading…
Reference in New Issue
Block a user