init
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[*.{js,jsx,ts,tsx,vue}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
const ALWAYS = 'always'
|
||||||
|
const NEVER = 'never'
|
||||||
|
const ERROR = 2
|
||||||
|
const WARN = 1
|
||||||
|
const OFF = 0
|
||||||
|
const PROD = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-strongly-recommended',
|
||||||
|
'@vue/standard'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2020
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': PROD ? ERROR : WARN,
|
||||||
|
'no-console': PROD ? ERROR : WARN,
|
||||||
|
'no-debugger': PROD ? ERROR : WARN,
|
||||||
|
'space-before-function-paren': [WARN, NEVER],
|
||||||
|
'vue/max-attributes-per-line': [ERROR, {
|
||||||
|
singleline: 5,
|
||||||
|
multiline: {
|
||||||
|
max: 1,
|
||||||
|
allowFirstLine: true
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
'vue/html-self-closing': OFF
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "npm run serve",
|
||||||
|
"name": "Start Client",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
declare module "*.vue" {
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue.defineComponent();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CodeWorks Vue Starter
|
||||||
|
=====================
|
||||||
|
This template is designed to help get students started building vue applications
|
||||||
|
|
||||||
|
## Legal Overview
|
||||||
|
|
||||||
|
The content under the CodeWorks®, LLC Organization and all of the individual repos are solely intended for use by CodeWorks Instruction to deliver Educational content to CodeWorks Students.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
© CodeWorks® LLC, 2021. Unauthorized use and/or duplication of this material without express and written permission from CodeWorks, LLC is strictly prohibited.
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://bcw.blob.core.windows.net/public/img/7815839041305055" width="125">
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="https://bcw.blob.core.windows.net/public/img/9977764104160066" />
|
||||||
|
<title>
|
||||||
|
keepr.client
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but keepr.client doesn't work properly without JavaScript
|
||||||
|
enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
<script type="module" src="./src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+5253
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "cw-vue-template",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bcwdev/auth0provider-client": "^0.0.12",
|
||||||
|
"@mdi/font": "^6.3.95",
|
||||||
|
"@popperjs/core": "^2.10.2",
|
||||||
|
"axios": "^0.23.0",
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
|
"socket.io-client": "^4.1.2",
|
||||||
|
"sweetalert2": "^11.1.9",
|
||||||
|
"vue": "^3.2.11",
|
||||||
|
"vue-router": "^4.0.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "1.9.3",
|
||||||
|
"@vue/compiler-sfc": "3.2.20",
|
||||||
|
"eslint": "8.0.1",
|
||||||
|
"eslint-plugin-import": "2.25.2",
|
||||||
|
"eslint-plugin-node": "11.1.0",
|
||||||
|
"eslint-plugin-vue": "7.19.1",
|
||||||
|
"sass": "1.32.4",
|
||||||
|
"vite": "2.6.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<Navbar />
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<router-view />
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<div class="bg-dark text-light text-center p-4">
|
||||||
|
Made with 💖 by CodeWorks
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { AppState } from './AppState'
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
appState: computed(() => AppState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
@import "./assets/scss/main.scss";
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
// NOTE AppState is a reactive object to contain app level data
|
||||||
|
export const AppState = reactive({
|
||||||
|
user: {},
|
||||||
|
account: {}
|
||||||
|
})
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,79 @@
|
|||||||
|
.skeleton-loader {
|
||||||
|
box-shadow: 0 4px 10px 0 rgba(33, 33, 33, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #dcdcdc;
|
||||||
|
pointer-events: none;
|
||||||
|
&.dark{
|
||||||
|
background-color: #9d9c9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.card{
|
||||||
|
background-color: #fff;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, transparent 0%, #f6f6f6 50%, transparent 100%);
|
||||||
|
animation: load 1.5s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.avatar{
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 45px;
|
||||||
|
width: 45px;
|
||||||
|
&.tile{
|
||||||
|
border-radius: 5%;
|
||||||
|
}
|
||||||
|
&.md{
|
||||||
|
height: 65px;
|
||||||
|
width: 65px;
|
||||||
|
}
|
||||||
|
&.lg{
|
||||||
|
height: 85px;
|
||||||
|
width: 85px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.text{
|
||||||
|
width: clamp(50%, 160px, 100%);
|
||||||
|
height: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
margin: .5rem 0;
|
||||||
|
&.sm{
|
||||||
|
width: clamp(25%, 80px, 100%);
|
||||||
|
}
|
||||||
|
&.lg{
|
||||||
|
width: clamp(75%, 250px, 100%);
|
||||||
|
}
|
||||||
|
&.block{
|
||||||
|
width: clamp(100%, 500px, 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 50px;
|
||||||
|
background: linear-gradient(to right, transparent 0%, #E8E8E8 50%, transparent 100%);
|
||||||
|
animation: load 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes load {
|
||||||
|
from {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.form-control {
|
||||||
|
background-color: #f7f5f5;
|
||||||
|
&:focus {
|
||||||
|
background-color: #fbf9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Transitions
|
||||||
|
|
||||||
|
// [+] ROUTER TRANSITION
|
||||||
|
// intentially empty by default
|
||||||
|
// if you want to enable router
|
||||||
|
// transtions change the settings below
|
||||||
|
.route-enter-active,
|
||||||
|
.route-leave-active {
|
||||||
|
transition: all 0s linear; // try changing 0s to .25s
|
||||||
|
}
|
||||||
|
.route-enter-from,
|
||||||
|
.route-leave-to {
|
||||||
|
opacity: 1; // try setting to 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.slideY-enter-active,
|
||||||
|
.slideY-leave-active {
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
.slideY-enter-from,
|
||||||
|
.slideY-leave-to {
|
||||||
|
transform: translateY(100vh);
|
||||||
|
}
|
||||||
|
.slideX-enter-active,
|
||||||
|
.slideX-leave-active {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
.slideX-enter-from,
|
||||||
|
.slideX-leave-to {
|
||||||
|
transform: translateX(100vw);
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
@function color-yiq($background) {
|
||||||
|
$r: red($background);
|
||||||
|
$g: green($background);
|
||||||
|
$b: blue($background);
|
||||||
|
|
||||||
|
$yiq: (($r * 299) + ($g * 587) + ($b * 114))/1000;
|
||||||
|
|
||||||
|
@if ($yiq >= 128) {
|
||||||
|
@return #111;
|
||||||
|
} @else {
|
||||||
|
@return #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@function fade($background, $alpha) {
|
||||||
|
$r: red($background);
|
||||||
|
$g: green($background);
|
||||||
|
$b: blue($background);
|
||||||
|
@return rgba($r, $g, $b, $alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $color, $value in $colors {
|
||||||
|
.bg-#{$color} {
|
||||||
|
color: color-yiq($value);
|
||||||
|
background-color: $value !important;
|
||||||
|
}
|
||||||
|
.text-#{$color} {
|
||||||
|
color: $value !important;
|
||||||
|
}
|
||||||
|
$num: 5;
|
||||||
|
@while $num < 100 {
|
||||||
|
$darker: darken($value, $num);
|
||||||
|
$lighter: lighten($value, $num);
|
||||||
|
.bg-#{$color} {
|
||||||
|
&.lighten-#{$num} {
|
||||||
|
color: color-yiq($lighter);
|
||||||
|
background-color: $lighter !important;
|
||||||
|
}
|
||||||
|
&.darken-#{$num} {
|
||||||
|
color: color-yiq($darker);
|
||||||
|
background-color: $darker !important;
|
||||||
|
}
|
||||||
|
&.fade-#{$num} {
|
||||||
|
background-color: fade($value, $num / 100) !important;
|
||||||
|
}
|
||||||
|
&.fade-lighten-#{$num} {
|
||||||
|
background-color: fade($lighter, $num / 100) !important;
|
||||||
|
}
|
||||||
|
&.fade-darken-#{$num} {
|
||||||
|
background-color: fade($darker, $num / 100) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text-#{$color}.darken-#{$num} {
|
||||||
|
color: $darker !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-#{$color}.lighten-#{$num} {
|
||||||
|
color: $lighter !important;
|
||||||
|
}
|
||||||
|
$num: $num + 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clip-text {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-x: hidden;
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.no-select {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-x {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.scrollable-y {
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
// opacity: 0.65;
|
||||||
|
&::after {
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: all;
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.25rem;
|
||||||
|
text-shadow: 1px 1px 0 #555, -1px -1px 0 #555, 1px -1px 0 #555, -1px 1px 0 #555, 1px 1px 0 #555;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(85, 85, 85, 0.37);
|
||||||
|
&.text-disabled {
|
||||||
|
content: "DISABLED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.on-hover {
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
*:hover > .on-hover {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-left {
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-right {
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-top {
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-bottom {
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-bottom-left {
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-bottom-right {
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-top-left {
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.square-top-right {
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectable {
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
&:after {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
border-radius: inherit;
|
||||||
|
bottom: 0;
|
||||||
|
color: inherit;
|
||||||
|
content: "";
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.6, 1);
|
||||||
|
background-color: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.elevation-1 {
|
||||||
|
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.elevation-2 {
|
||||||
|
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.elevation-3 {
|
||||||
|
box-shadow: 0 3px 3px -2px rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14), 0 1px 8px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.elevation-4 {
|
||||||
|
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.elevation-5 {
|
||||||
|
box-shadow: 0 2px 5px -1px rgba(0, 0, 0, 0.2), 0 5px 6px 0 rgba(0, 0, 0, 0.14), 0 1px 12px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-8 {
|
||||||
|
font-size: 8pt !important;
|
||||||
|
}
|
||||||
|
.f-10 {
|
||||||
|
font-size: 10pt !important;
|
||||||
|
}
|
||||||
|
.f-12 {
|
||||||
|
font-size: 12pt !important;
|
||||||
|
}
|
||||||
|
.f-14 {
|
||||||
|
font-size: 14pt !important;
|
||||||
|
}
|
||||||
|
.f-16 {
|
||||||
|
font-size: 16pt !important;
|
||||||
|
}
|
||||||
|
.f-18 {
|
||||||
|
font-size: 18pt !important;
|
||||||
|
}
|
||||||
|
.f-20 {
|
||||||
|
font-size: 20pt !important;
|
||||||
|
}
|
||||||
|
.f-22 {
|
||||||
|
font-size: 22pt !important;
|
||||||
|
}
|
||||||
|
.f-24 {
|
||||||
|
font-size: 24pt !important;
|
||||||
|
}
|
||||||
|
.f-26 {
|
||||||
|
font-size: 26pt !important;
|
||||||
|
}
|
||||||
|
.f-28 {
|
||||||
|
font-size: 28pt !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.f-8 {
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-10 {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-12 {
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-14 {
|
||||||
|
font-size: 0.95rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-16 {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-18 {
|
||||||
|
font-size: 1.15rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-20 {
|
||||||
|
font-size: 1.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-22 {
|
||||||
|
font-size: 1.34rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-24 {
|
||||||
|
font-size: 1.45rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-26 {
|
||||||
|
font-size: 1.65rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-28 {
|
||||||
|
font-size: 1.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-30 {
|
||||||
|
font-size: 2rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
$body-bg: #e5e5e5;
|
||||||
|
$primary: #3ba5dc;
|
||||||
|
$secondary: #8392ab;
|
||||||
|
$success: #00f867;
|
||||||
|
$info: #5d0149;
|
||||||
|
$warning: #fbcf33;
|
||||||
|
$danger: #ea0606;
|
||||||
|
$light: #e9ecef;
|
||||||
|
$dark: #111927;
|
||||||
|
$grey: #cbcbcb;
|
||||||
|
|
||||||
|
$font-sans-serif: Open Sans;
|
||||||
|
$font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
$gradient: linear-gradient(180deg, hsla(0, 0%, 100%, 0.15), hsla(0, 0%, 100%, 0));
|
||||||
|
|
||||||
|
$colors: (
|
||||||
|
"primary": $primary,
|
||||||
|
"secondary": $secondary,
|
||||||
|
"danger": $danger,
|
||||||
|
"success": $success,
|
||||||
|
"warning": $warning,
|
||||||
|
"info": $info,
|
||||||
|
"dark": $dark,
|
||||||
|
"grey": $grey,
|
||||||
|
);
|
||||||
|
|
||||||
|
$color-ranges: (
|
||||||
|
0: 0,
|
||||||
|
5: 5,
|
||||||
|
10: 10,
|
||||||
|
15: 15,
|
||||||
|
20: 20,
|
||||||
|
25: 25,
|
||||||
|
30: 30,
|
||||||
|
35: 35,
|
||||||
|
40: 40,
|
||||||
|
);
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
@import "./_variables.scss";
|
||||||
|
@import "bootstrap";
|
||||||
|
@import "./_overrides.scss";
|
||||||
|
@import "./utilities.scss";
|
||||||
|
@import "./loaders.scss";
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: $body-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
&:not(:placeholder-shown) {
|
||||||
|
&:valid {
|
||||||
|
border: 1px solid $success;
|
||||||
|
}
|
||||||
|
&:invalid {
|
||||||
|
border: 1px solid $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form:invalid button[type="submit"] {
|
||||||
|
filter: grayscale(1);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<span class="navbar-text">
|
||||||
|
<button
|
||||||
|
class="btn selectable text-success lighten-30 text-uppercase my-2 my-lg-0"
|
||||||
|
@click="login"
|
||||||
|
v-if="!user.isAuthenticated"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="dropdown my-2 my-lg-0" v-else>
|
||||||
|
<div
|
||||||
|
class="dropdown-toggle selectable"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
id="authDropdown"
|
||||||
|
>
|
||||||
|
<div v-if="account.picture">
|
||||||
|
<img
|
||||||
|
:src="account.picture"
|
||||||
|
alt="account photo"
|
||||||
|
height="40"
|
||||||
|
class="rounded"
|
||||||
|
/>
|
||||||
|
<span class="mx-3 text-success lighten-30">{{ account.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dropdown-menu p-0 list-group w-100"
|
||||||
|
aria-labelledby="authDropdown"
|
||||||
|
>
|
||||||
|
<router-link :to="{ name: 'Account' }">
|
||||||
|
<div class="list-group-item list-group-item-action hoverable">
|
||||||
|
Manage Account
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
<div
|
||||||
|
class="list-group-item list-group-item-action hoverable text-danger"
|
||||||
|
@click="logout"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-logout"></i>
|
||||||
|
logout
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed } from "@vue/reactivity";
|
||||||
|
import { AppState } from "../AppState";
|
||||||
|
import { AuthService } from "../services/AuthService";
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
user: computed(() => AppState.user),
|
||||||
|
account: computed(() => AppState.account),
|
||||||
|
async login() {
|
||||||
|
AuthService.loginWithPopup();
|
||||||
|
},
|
||||||
|
async logout() {
|
||||||
|
AuthService.logout({ returnTo: window.location.origin });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dropdown-menu {
|
||||||
|
user-select: none;
|
||||||
|
display: block;
|
||||||
|
transform: scale(0);
|
||||||
|
transition: all 0.15s linear;
|
||||||
|
}
|
||||||
|
.dropdown-menu.show {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
.hoverable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark px-3">
|
||||||
|
<router-link class="navbar-brand d-flex" :to="{ name: 'Home' }">
|
||||||
|
<div class="d-flex flex-column align-items-center">
|
||||||
|
<img alt="logo" src="../assets/img/cw-logo.png" height="45" />
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarText"
|
||||||
|
aria-controls="navbarText"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon" />
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarText">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'About' }"
|
||||||
|
class="btn text-success lighten-30 selectable text-uppercase"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!-- LOGIN COMPONENT HERE -->
|
||||||
|
<Login />
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.navbar-nav .router-link-exact-active {
|
||||||
|
border-bottom: 2px solid var(--bs-success);
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const dev = window.location.origin.includes('localhost')
|
||||||
|
export const baseURL = dev ? 'https://localhost:5001' : ''
|
||||||
|
export const useSockets = false
|
||||||
|
export const domain = 'dev-h9kk6tgd.us.auth0.com'
|
||||||
|
export const clientId = 'C1FImfihED3KA1bZk7F3JsGg5CyjwrdX'
|
||||||
|
export const audience = 'https://AnniDev.com'
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
import 'bootstrap'
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
// @ts-ignore
|
||||||
|
import App from './App.vue'
|
||||||
|
import { registerGlobalComponents } from './registerGlobalComponents'
|
||||||
|
import { router } from './router'
|
||||||
|
|
||||||
|
const root = createApp(App)
|
||||||
|
registerGlobalComponents(root)
|
||||||
|
|
||||||
|
root
|
||||||
|
.use(router)
|
||||||
|
.mount('#app')
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is the about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AboutPage'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about text-center">
|
||||||
|
<h1>Welcome {{ account.name }}</h1>
|
||||||
|
<img class="rounded" :src="account.picture" alt="" />
|
||||||
|
<p>{{ account.email }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { AppState } from '../AppState'
|
||||||
|
export default {
|
||||||
|
name: 'Account',
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
account: computed(() => AppState.account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
img {
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home flex-grow-1 d-flex flex-column align-items-center justify-content-center">
|
||||||
|
<div class="home-card p-5 bg-white rounded elevation-3">
|
||||||
|
<img src="https://bcw.blob.core.windows.net/public/img/8600856373152463" alt="CodeWorks Logo" class="rounded-circle">
|
||||||
|
<h1 class="my-5 bg-dark text-white p-3 rounded text-center">
|
||||||
|
Vue 3 Starter
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Home'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.home{
|
||||||
|
display: grid;
|
||||||
|
height: 80vh;
|
||||||
|
place-content: center;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
.home-card{
|
||||||
|
width: 50vw;
|
||||||
|
> img{
|
||||||
|
height: 200px;
|
||||||
|
max-width: 200px;
|
||||||
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export function registerGlobalComponents(root) {
|
||||||
|
// https://webpack.js.org/guides/dependency-management/#require-context
|
||||||
|
// @ts-ignore
|
||||||
|
const components = import.meta.globEager('./components/*.vue')
|
||||||
|
Object.entries(components).forEach(([fileName, component]) => {
|
||||||
|
const componentName = component.name || fileName
|
||||||
|
.substr(fileName.lastIndexOf('/') + 1)
|
||||||
|
.replace(/\.\w+$/, '')
|
||||||
|
// Register component on this Vue instance
|
||||||
|
root.component(componentName, component.default)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import { authGuard } from '@bcwdev/auth0provider-client'
|
||||||
|
|
||||||
|
function loadPage(page) {
|
||||||
|
return () => import(`./pages/${page}.vue`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: loadPage('HomePage')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'About',
|
||||||
|
component: loadPage('AboutPage')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account',
|
||||||
|
name: 'Account',
|
||||||
|
component: loadPage('AccountPage'),
|
||||||
|
beforeEnter: authGuard
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
linkActiveClass: 'router-link-active',
|
||||||
|
linkExactActiveClass: 'router-link-exact-active',
|
||||||
|
history: createWebHashHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { AppState } from '../AppState'
|
||||||
|
import { logger } from '../utils/Logger'
|
||||||
|
import { api } from './AxiosService'
|
||||||
|
|
||||||
|
class AccountService {
|
||||||
|
async getAccount() {
|
||||||
|
try {
|
||||||
|
const res = await api.get('/account')
|
||||||
|
AppState.account = res.data
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('HAVE YOU STARTED YOUR SERVER YET???', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const accountService = new AccountService()
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { initialize } from '@bcwdev/auth0provider-client'
|
||||||
|
import { AppState } from '../AppState'
|
||||||
|
import { audience, clientId, domain } from '../env'
|
||||||
|
import { router } from '../router'
|
||||||
|
import { accountService } from './AccountService'
|
||||||
|
import { api } from './AxiosService'
|
||||||
|
import { socketService } from './SocketService'
|
||||||
|
|
||||||
|
export const AuthService = initialize({
|
||||||
|
domain,
|
||||||
|
clientId,
|
||||||
|
audience,
|
||||||
|
useRefreshTokens: true,
|
||||||
|
onRedirectCallback: appState => {
|
||||||
|
router.push(
|
||||||
|
appState && appState.targetUrl
|
||||||
|
? appState.targetUrl
|
||||||
|
: window.location.pathname
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
AuthService.on(AuthService.AUTH_EVENTS.AUTHENTICATED, async function() {
|
||||||
|
api.defaults.headers.authorization = AuthService.bearer
|
||||||
|
api.interceptors.request.use(refreshAuthToken)
|
||||||
|
AppState.user = AuthService.user
|
||||||
|
await accountService.getAccount()
|
||||||
|
socketService.authenticate(AuthService.bearer)
|
||||||
|
// NOTE if there is something you want to do once the user is authenticated, place that here
|
||||||
|
})
|
||||||
|
|
||||||
|
async function refreshAuthToken(config) {
|
||||||
|
if (!AuthService.isAuthenticated) { return config }
|
||||||
|
const expires = AuthService.identity.exp * 1000
|
||||||
|
const expired = expires < Date.now()
|
||||||
|
const needsRefresh = expires < Date.now() + (1000 * 60 * 60 * 12)
|
||||||
|
if (expired) {
|
||||||
|
await AuthService.loginWithPopup()
|
||||||
|
} else if (needsRefresh) {
|
||||||
|
await AuthService.getTokenSilently()
|
||||||
|
api.defaults.headers.authorization = AuthService.bearer
|
||||||
|
socketService.authenticate(AuthService.bearer)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Axios from 'axios'
|
||||||
|
import { baseURL } from '../env'
|
||||||
|
export const api = Axios.create({
|
||||||
|
baseURL,
|
||||||
|
timeout: 8000
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import Pop from '../utils/Pop'
|
||||||
|
import { SocketHandler } from '../utils/SocketHandler'
|
||||||
|
|
||||||
|
class SocketService extends SocketHandler {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this
|
||||||
|
.on('error', this.onError)
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(e) {
|
||||||
|
Pop.toast(e.message, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const socketService = new SocketService()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { dev } from '../env'
|
||||||
|
|
||||||
|
function log(type, content) {
|
||||||
|
if (dev) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console[type](`[${type}] :: ${new Date().toLocaleTimeString()} :: `, ...content)
|
||||||
|
} else {
|
||||||
|
switch (type) {
|
||||||
|
case 'log':
|
||||||
|
case 'assert':
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO SEND LOGS TO EXTERNAL SERVICE
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console[type](`[${type}] :: ${new Date().toLocaleTimeString()} :: `, ...content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logger = {
|
||||||
|
log() {
|
||||||
|
log('log', arguments)
|
||||||
|
},
|
||||||
|
error() {
|
||||||
|
log('error', arguments)
|
||||||
|
},
|
||||||
|
warn() {
|
||||||
|
log('warn', arguments)
|
||||||
|
},
|
||||||
|
assert() {
|
||||||
|
log('assert', arguments)
|
||||||
|
},
|
||||||
|
trace() {
|
||||||
|
log('trace', arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import Swal from 'sweetalert2'
|
||||||
|
import 'sweetalert2/dist/sweetalert2.min.css'
|
||||||
|
|
||||||
|
export default class Pop {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} title The title text.
|
||||||
|
* @param {string} text The body text.
|
||||||
|
* @param {string} icon 'success', 'error', 'info', 'warning', or 'question'.
|
||||||
|
* @param {string} confirmButtonText The text of your confirm button.
|
||||||
|
* -----------------------------------
|
||||||
|
* {@link https://sweetalert2.github.io/#configuration|Check out Sweet Alerts}
|
||||||
|
*/
|
||||||
|
static async confirm(title = 'Are you sure?', text = "You won't be able to revert this!", icon = 'warning', confirmButtonText = 'Yes, delete it!') {
|
||||||
|
try {
|
||||||
|
const res = await Swal.fire({
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
icon: icon,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#3085d6',
|
||||||
|
cancelButtonColor: '#d33',
|
||||||
|
confirmButtonText: confirmButtonText
|
||||||
|
})
|
||||||
|
if (res.isConfirmed) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} title The title text
|
||||||
|
* @param {string} display 'success', 'error', 'info', 'warning', or 'question'.
|
||||||
|
* @param {string} position 'top', 'top-start', 'top-end', 'center', 'center-start', 'center-end', 'bottom', 'bottom-start', or 'bottom-end'.
|
||||||
|
* @param {number} timer Time in milliseconds.
|
||||||
|
* @param {boolean} progressBar Show progress bar or not respectively.
|
||||||
|
* -----------------------------------
|
||||||
|
* {@link https://sweetalert2.github.io/#configuration|Check out Sweet Alerts}
|
||||||
|
*/
|
||||||
|
static toast(title = 'Warning!', display = 'warning', position = 'top-end', timer = 3000, progressBar = true) {
|
||||||
|
Swal.fire({
|
||||||
|
title: title,
|
||||||
|
icon: display,
|
||||||
|
position: position,
|
||||||
|
timer: timer,
|
||||||
|
timerProgressBar: progressBar,
|
||||||
|
toast: true,
|
||||||
|
showConfirmButton: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('axios').AxiosError | Error | String } Error An Error Object.
|
||||||
|
*/
|
||||||
|
static error(error) {
|
||||||
|
if (error.isAxiosError) {
|
||||||
|
const { response } = error
|
||||||
|
this.toast(response.data.error?.message || response.data.message, 'error')
|
||||||
|
} else {
|
||||||
|
this.toast(error.message || error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { String } message The message to display. If not provided, will display a generic message.
|
||||||
|
*/
|
||||||
|
static success(message = 'Success!') {
|
||||||
|
this.toast(message, 'success')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { io } from 'socket.io-client'
|
||||||
|
import { baseURL, useSockets } from '../env.js'
|
||||||
|
import { logger } from './Logger.js'
|
||||||
|
|
||||||
|
const SOCKET_EVENTS = {
|
||||||
|
connection: 'connection',
|
||||||
|
connected: 'connected',
|
||||||
|
disconnect: 'disconnect',
|
||||||
|
authenticate: 'authenticate',
|
||||||
|
authenticated: 'authenticated',
|
||||||
|
userConnected: 'userConnected',
|
||||||
|
userDisconnected: 'userDisconnected',
|
||||||
|
error: 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SocketHandler {
|
||||||
|
/**
|
||||||
|
* @param {String} url
|
||||||
|
*/
|
||||||
|
constructor(requiresAuth = false, url = baseURL) {
|
||||||
|
if (!useSockets) { return }
|
||||||
|
this.socket = io(url || baseURL)
|
||||||
|
this.requiresAuth = requiresAuth
|
||||||
|
this.queue = []
|
||||||
|
this.authenticated = false
|
||||||
|
this
|
||||||
|
.on(SOCKET_EVENTS.connected, this.onConnected)
|
||||||
|
.on(SOCKET_EVENTS.authenticated, this.onAuthenticated)
|
||||||
|
.on(SOCKET_EVENTS.error, this.onError)
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, fn) {
|
||||||
|
this.socket?.on(event, fn.bind(this))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnected(connection) {
|
||||||
|
logger.log('[SOCKET_CONNECTION]', connection)
|
||||||
|
this.connected = true
|
||||||
|
this.playback()
|
||||||
|
}
|
||||||
|
|
||||||
|
onAuthenticated(auth) {
|
||||||
|
logger.log('[SOCKET_AUTHENTICATED]', auth)
|
||||||
|
this.authenticated = true
|
||||||
|
this.playback()
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate(bearerToken) {
|
||||||
|
this.socket?.emit(SOCKET_EVENTS.authenticate, bearerToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(error) {
|
||||||
|
logger.error('[SOCKET_ERROR]', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueue(action, payload) {
|
||||||
|
logger.log('[ENQUEING_ACTION]', { action, payload })
|
||||||
|
this.queue.push({ action, payload })
|
||||||
|
}
|
||||||
|
|
||||||
|
playback() {
|
||||||
|
logger.log('[SOCKET_PLAYBACK]')
|
||||||
|
const playback = [...this.queue]
|
||||||
|
this.queue = []
|
||||||
|
playback.forEach(e => {
|
||||||
|
this.emit(e.action, e.payload)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(action, payload = undefined) {
|
||||||
|
if (this.requiresAuth && !this.authenticated) {
|
||||||
|
return this.enqueue(action, payload)
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
return this.enqueue(action, payload)
|
||||||
|
}
|
||||||
|
this.socket.emit(action, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
build: {
|
||||||
|
outDir: 'docs',
|
||||||
|
sourcemap: false
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 8080
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "keepr.client",
|
||||||
|
"name": "client"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "keepr",
|
||||||
|
"name": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keepr",
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"editor.suggestSelection": "first",
|
||||||
|
"git.enableSmartCommit": true,
|
||||||
|
"git.confirmSync": false,
|
||||||
|
"git.autofetch": true,
|
||||||
|
"javascript.referencesCodeLens.enabled": true,
|
||||||
|
"javascript.referencesCodeLens.showOnAllFunctions": true,
|
||||||
|
"typescript.implementationsCodeLens.enabled": true,
|
||||||
|
"typescript.referencesCodeLens.enabled": true,
|
||||||
|
"typescript.referencesCodeLens.showOnAllFunctions": true,
|
||||||
|
"js/ts.implicitProjectConfig.checkJs": true,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.nyc_*": true,
|
||||||
|
"**/coverage": true,
|
||||||
|
"**/node_modules": true
|
||||||
|
},
|
||||||
|
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"explorer.confirmDragAndDrop": false,
|
||||||
|
"vetur.format.defaultFormatter.js": "vscode-typescript",
|
||||||
|
"eslint.alwaysShowStatus": true,
|
||||||
|
"eslint.format.enable": true,
|
||||||
|
"eslint.lintTask.enable": true,
|
||||||
|
"eslint.codeActionsOnSave.mode": "all",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": true
|
||||||
|
},
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"activityBar.activeBackground": "#65c89b",
|
||||||
|
"activityBar.activeBorder": "#945bc4",
|
||||||
|
"activityBar.background": "#65c89b",
|
||||||
|
"activityBar.foreground": "#15202b",
|
||||||
|
"activityBar.inactiveForeground": "#15202b99",
|
||||||
|
"activityBarBadge.background": "#945bc4",
|
||||||
|
"activityBarBadge.foreground": "#e7e7e7",
|
||||||
|
"sash.hoverBorder": "#65c89b",
|
||||||
|
"statusBar.background": "#42b883",
|
||||||
|
"statusBar.foreground": "#15202b",
|
||||||
|
"statusBarItem.hoverBackground": "#359268",
|
||||||
|
"statusBarItem.remoteBackground": "#42b883",
|
||||||
|
"statusBarItem.remoteForeground": "#15202b",
|
||||||
|
"titleBar.activeBackground": "#42b883",
|
||||||
|
"titleBar.activeForeground": "#15202b",
|
||||||
|
"titleBar.inactiveBackground": "#42b88399",
|
||||||
|
"titleBar.inactiveForeground": "#15202b99"
|
||||||
|
},
|
||||||
|
"peacock.color": "#42b883"
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+47
@@ -0,0 +1,47 @@
|
|||||||
|
# Your workflow name.
|
||||||
|
name: Deploy to heroku.
|
||||||
|
|
||||||
|
# Run workflow on every push to master branch.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
# Your workflows jobs.
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Check-out your repository.
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup .NET Core
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: 6.0.x
|
||||||
|
|
||||||
|
- name: Set Env Vars
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: 'keepr/appsettings.json'
|
||||||
|
env:
|
||||||
|
CONNECTION_STRING: ${{ secrets.CONNECTION_STRING }}
|
||||||
|
AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }}
|
||||||
|
AUTH0_AUDIENCE: ${{ secrets.AUTH0_AUDIENCE }}
|
||||||
|
|
||||||
|
# dotnet build and publish
|
||||||
|
- name: dotnet publish
|
||||||
|
run: |
|
||||||
|
dotnet publish ./keepr -c Release
|
||||||
|
|
||||||
|
- name: Build, Push and Release a Docker container to Heroku.
|
||||||
|
uses: gonuit/heroku-docker-deploy@v1.3.3
|
||||||
|
with:
|
||||||
|
# The email address associated with your Heroku account.
|
||||||
|
email: ${{ secrets.HEROKU_EMAIL }}
|
||||||
|
# Heroku API key associated with provided user's email.
|
||||||
|
# Api Key is available under your Heroku account settings.
|
||||||
|
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
|
||||||
|
# Name of the heroku application to which the build is to be sent.
|
||||||
|
heroku_app_name: ${{ secrets.HEROKU_APP_NAME }}
|
||||||
|
dockerfile_directory: ./keepr
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# appsettings
|
||||||
|
appsettings.development.json
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*[.json, .xml, .info]
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual Studio Code
|
||||||
|
##
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
Vendored
+35
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||||
|
"name": ".NET Core Launch (web)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/net6.0/keepr.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
|
||||||
|
"serverReadyAction": {
|
||||||
|
"action": "openExternally",
|
||||||
|
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"sourceFileMap": {
|
||||||
|
"/Views": "${workspaceFolder}/Views"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/keepr.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/keepr.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/keepr.csproj"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using keepr.Models;
|
||||||
|
using keepr.Services;
|
||||||
|
using CodeWorks.Auth0Provider;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace keepr.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class AccountController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AccountService _accountService;
|
||||||
|
|
||||||
|
public AccountController(AccountService accountService)
|
||||||
|
{
|
||||||
|
_accountService = accountService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Account>> Get()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Account userInfo = await HttpContext.GetUserInfoAsync<Account>();
|
||||||
|
return Ok(_accountService.GetOrCreateProfile(userInfo));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return BadRequest(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0
|
||||||
|
COPY ./bin/Release/net5.0/publish/ App/
|
||||||
|
WORKDIR /App
|
||||||
|
CMD ASPNETCORE_URLS=http://*:$PORT dotnet keepr.dll
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Deploy dotnet container to Heroku
|
||||||
|
|
||||||
|
To deploy this application to Heroku using Docker Containers you will need to add several repository secrets and move the .github workflow to the root of this project.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace keepr.Models
|
||||||
|
{
|
||||||
|
public class Profile
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Picture { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Account : Profile
|
||||||
|
{
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
||||||
|
namespace keepr.Models
|
||||||
|
{
|
||||||
|
public class Keep
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string creatorId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Img { get; set; }
|
||||||
|
public int Views { get; set; }
|
||||||
|
public int Kept { get; set; }
|
||||||
|
|
||||||
|
// populate creator?
|
||||||
|
|
||||||
|
// shares int - stretch goal
|
||||||
|
}
|
||||||
|
|
||||||
|
public class
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace keepr.Models
|
||||||
|
{
|
||||||
|
public class Vault
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string CreatorId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public bool IsPrivate { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Do I really need a model for this intermediate data structure?
|
||||||
|
|
||||||
|
|
||||||
|
namespace keepr.Models
|
||||||
|
{
|
||||||
|
public class VaultKeep
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string CreatorId { get; set; }
|
||||||
|
public int VaultId { get; set; }
|
||||||
|
public int KeepId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace keepr
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:13353",
|
||||||
|
"sslPort": 44303
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keepr": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": "true",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Data;
|
||||||
|
using keepr.Models;
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace keepr.Repositories
|
||||||
|
{
|
||||||
|
public class AccountsRepository
|
||||||
|
{
|
||||||
|
private readonly IDbConnection _db;
|
||||||
|
|
||||||
|
public AccountsRepository(IDbConnection db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Account GetByEmail(string userEmail)
|
||||||
|
{
|
||||||
|
string sql = "SELECT * FROM accounts WHERE email = @userEmail";
|
||||||
|
return _db.QueryFirstOrDefault<Account>(sql, new { userEmail });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Account GetById(string id)
|
||||||
|
{
|
||||||
|
string sql = "SELECT * FROM accounts WHERE id = @id";
|
||||||
|
return _db.QueryFirstOrDefault<Account>(sql, new { id });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Account Create(Account newAccount)
|
||||||
|
{
|
||||||
|
string sql = @"
|
||||||
|
INSERT INTO accounts
|
||||||
|
(name, picture, email, id)
|
||||||
|
VALUES
|
||||||
|
(@Name, @Picture, @Email, @Id)";
|
||||||
|
_db.Execute(sql, newAccount);
|
||||||
|
return newAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Account Edit(Account update)
|
||||||
|
{
|
||||||
|
string sql = @"
|
||||||
|
UPDATE accounts
|
||||||
|
SET
|
||||||
|
name = @Name,
|
||||||
|
picture = @Picture
|
||||||
|
WHERE id = @Id;";
|
||||||
|
_db.Execute(sql, update);
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using keepr.Models;
|
||||||
|
using keepr.Repositories;
|
||||||
|
|
||||||
|
namespace keepr.Services
|
||||||
|
{
|
||||||
|
public class AccountService
|
||||||
|
{
|
||||||
|
private readonly AccountsRepository _repo;
|
||||||
|
public AccountService(AccountsRepository repo)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string GetProfileEmailById(string id)
|
||||||
|
{
|
||||||
|
return _repo.GetById(id).Email;
|
||||||
|
}
|
||||||
|
internal Account GetProfileByEmail(string email)
|
||||||
|
{
|
||||||
|
return _repo.GetByEmail(email);
|
||||||
|
}
|
||||||
|
internal Account GetOrCreateProfile(Account userInfo)
|
||||||
|
{
|
||||||
|
Account profile = _repo.GetById(userInfo.Id);
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
return _repo.Create(userInfo);
|
||||||
|
}
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Account Edit(Account editData, string userEmail)
|
||||||
|
{
|
||||||
|
Account original = GetProfileByEmail(userEmail);
|
||||||
|
original.Name = editData.Name.Length > 0 ? editData.Name : original.Name;
|
||||||
|
original.Picture = editData.Picture.Length > 0 ? editData.Picture : original.Picture;
|
||||||
|
return _repo.Edit(original);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using MySqlConnector;
|
||||||
|
using keepr.Repositories;
|
||||||
|
using keepr.Services;
|
||||||
|
|
||||||
|
namespace keepr
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
ConfigureCors(services);
|
||||||
|
ConfigureAuth(services);
|
||||||
|
services.AddControllers();
|
||||||
|
services.AddSwaggerGen(c =>
|
||||||
|
{
|
||||||
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "keepr", Version = "v1" });
|
||||||
|
});
|
||||||
|
services.AddScoped<IDbConnection>(x => CreateDbConnection());
|
||||||
|
|
||||||
|
services.AddScoped<AccountsRepository>();
|
||||||
|
services.AddScoped<AccountService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureCors(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy("CorsDevPolicy", builder =>
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowCredentials()
|
||||||
|
.WithOrigins(new string[]{
|
||||||
|
"http://localhost:8080", "http://localhost:8081"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureAuth(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
}).AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
options.Authority = $"https://{Configuration["AUTH0_DOMAIN"]}/";
|
||||||
|
options.Audience = Configuration["AUTH0_AUDIENCE"];
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDbConnection CreateDbConnection()
|
||||||
|
{
|
||||||
|
string connectionString = Configuration["CONNECTION_STRING"];
|
||||||
|
return new MySqlConnection(connectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "keepr v1"));
|
||||||
|
app.UseCors("CorsDevPolicy");
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseDefaultFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapControllers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"CONNECTION_STRING": "server=SG-Azula-6401-mysql-master.servers.mongodirector.com;port=3306;database=keepr;user id=keepr;password=sPZ6wy4nbfHj!NE;",
|
||||||
|
"AUTH0_DOMAIN": "dev-h9kk6tgd.us.auth0.com",
|
||||||
|
"AUTH0_AUDIENCE": "https://AnniDev.com"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS accounts(
|
||||||
|
id VARCHAR(255) NOT NULL primary key COMMENT 'primary key',
|
||||||
|
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'Time Created',
|
||||||
|
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last Update',
|
||||||
|
name varchar(255) COMMENT 'User Name',
|
||||||
|
email varchar(255) COMMENT 'User Email',
|
||||||
|
picture varchar(255) COMMENT 'User Picture'
|
||||||
|
) default charset utf8 COMMENT '';
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CodeWorks.Auth0Provider" Version="1.0.3" />
|
||||||
|
<PackageReference Include="Dapper" Version="2.0.90" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" NoWarn="NU1605" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.1" NoWarn="NU1605" />
|
||||||
|
<PackageReference Include="MySqlConnector" Version="2.1.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>keepr</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>keepr Initialized</h1>
|
||||||
|
<p>Check out the api swagger docs <a href="/swagger">here</a> </p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user