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