This commit is contained in:
Annika
2022-07-31 12:33:10 -06:00
commit 3eaefbf02a
58 changed files with 7540 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
> 1%
last 2 versions
+5
View File
@@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
+34
View File
@@ -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
}
}
+20
View File
@@ -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?
+14
View File
@@ -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"
}
]
}
+4
View File
@@ -0,0 +1,4 @@
declare module "*.vue" {
import Vue from 'vue';
export default Vue.defineComponent();
}
+16
View File
@@ -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">
+24
View File
@@ -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>
+5
View File
@@ -0,0 +1,5 @@
{
"include": [
"./src/**/*"
]
}
+5253
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -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"
}
}
+30
View File
@@ -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>
+7
View File
@@ -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,
);
+38
View File
@@ -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;
}
+84
View File
@@ -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>
+56
View File
@@ -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>
+6
View File
@@ -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'
+14
View File
@@ -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')
+11
View File
@@ -0,0 +1,11 @@
<template>
<div class="about">
<h1>This is the about page</h1>
</div>
</template>
<script>
export default {
name: 'AboutPage'
}
</script>
+26
View File
@@ -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>
+36
View File
@@ -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)
})
}
+32
View File
@@ -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()
+45
View File
@@ -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()
+35
View File
@@ -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)
}
}
+74
View File
@@ -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')
}
}
+80
View File
@@ -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)
}
}
+14
View File
@@ -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
}
})
+64
View File
@@ -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"
}
}
+47
View File
@@ -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
+445
View File
@@ -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
+35
View File
@@ -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"
}
]
}
+41
View File
@@ -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"
}
]
}
+40
View File
@@ -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);
}
}
}
}
+4
View File
@@ -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
+3
View File
@@ -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.
+17
View File
@@ -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; }
}
}
+23
View File
@@ -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
}
+15
View File
@@ -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; }
}
}
+13
View File
@@ -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; }
}
}
+26
View File
@@ -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>();
});
}
}
+31
View File
@@ -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"
}
}
}
}
+51
View File
@@ -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;
}
}
}
+40
View File
@@ -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);
}
}
}
+114
View File
@@ -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();
});
}
}
}
+5
View File
@@ -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"
}
+8
View File
@@ -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 '';
+17
View File
@@ -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>
+13
View File
@@ -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>