Renamed everything from MultiShop to Props.
43
Props/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
// 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}/server/bin/Debug/net5.0/Props.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/server",
|
||||
"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",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
]
|
||||
}
|
11
Props/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"eslint.workingDirectories": [
|
||||
"./client"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"vue",
|
||||
"javascript",
|
||||
],
|
||||
"javascript.preferences.quoteStyle": "double",
|
||||
"vetur.format.options.tabSize": 4
|
||||
}
|
59
Props/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build app",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/server/Props.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish app",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/server/Props.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch all",
|
||||
"command": "py",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"${workspaceFolder}/scripts/watch_all.py"
|
||||
],
|
||||
"problemMatcher": ["$msCompile", "$node-sass", "$jshint"],
|
||||
},
|
||||
{
|
||||
"label": "watch server",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"-p",
|
||||
"${workspaceFolder}/server/Props.csproj",
|
||||
"run"
|
||||
],
|
||||
"problemMatcher": ["$msCompile"]
|
||||
},
|
||||
{
|
||||
"label": "reset database",
|
||||
"command": "py",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"${workspaceFolder}/scripts/reset_db.py"
|
||||
],
|
||||
"problemMatcher": ["$msCompile"]
|
||||
}
|
||||
]
|
||||
}
|
3
Props/client/.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
6
Props/client/.editorconfig
Normal file
@@ -0,0 +1,6 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
quote_type = double
|
33
Props/client/.eslintrc.js
Normal file
@@ -0,0 +1,33 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'quotes': ['error', 'double', 'avoid-escape'],
|
||||
'semi': ['error', 'always'],
|
||||
'indent': ['error', 4],
|
||||
'comma-dangle': ['error', 'only-multiline'],
|
||||
'space-before-function-paren': ['error', 'never']
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.{j,t}s?(x)',
|
||||
'**/tests/unit/**/*.spec.{j,t}s?(x)'
|
||||
],
|
||||
env: {
|
||||
mocha: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
25
Props/client/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
/tests/e2e/logs/
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
34
Props/client/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# props
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Run your unit tests
|
||||
```
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
### Run your end-to-end tests
|
||||
```
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
5
Props/client/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
5
Props/client/jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
18602
Props/client/package-lock.json
generated
Normal file
48
Props/client/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "props",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"test:e2e": "vue-cli-service test:e2e",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"bootstrap": "^5.0.2",
|
||||
"bootstrap-icons": "^1.5.0",
|
||||
"core-js": "^3.6.5",
|
||||
"oidc-client": "^1.11.5",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-e2e-webdriverio": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-pwa": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-unit-mocha": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-standard": "^5.1.2",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"chai": "^4.1.2",
|
||||
"chromedriver": "91",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"sass": "^1.35.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"wdio-chromedriver-service": "^6.0.3"
|
||||
}
|
||||
}
|
13
Props/client/public/authentication/callback-handler.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { UserManager, WebStorageStateStore } from "oidc-client";
|
||||
|
||||
const userManager = new UserManager({
|
||||
authority: window.location.origin,
|
||||
client_id: "Props",
|
||||
redirect_uri: window.location.origin + "/authentication/login-callback",
|
||||
post_logout_redirect_uri: window.location.origin + "/authentication/logout-callback",
|
||||
response_type: "code",
|
||||
scope: "openid profile",
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
});
|
||||
|
||||
userManager.signinSilentCallback();
|
@@ -0,0 +1,16 @@
|
||||
<!-- Completely separate static html should improve silent login performance as we don't need to load entire SPA. -->
|
||||
<!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>authentication</title>
|
||||
<script type="module" src="callback-handler.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Silently authenticating user. If you are seeing this page, you probably want to <a href="/">go back to the app</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
Props/client/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
Props/client/public/img/icons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
Props/client/public/img/icons/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 22 KiB |
BIN
Props/client/public/img/icons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Props/client/public/img/icons/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
Props/client/public/img/icons/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
Props/client/public/img/icons/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Props/client/public/img/icons/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Props/client/public/img/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
Props/client/public/img/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 799 B |
BIN
Props/client/public/img/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Props/client/public/img/icons/msapplication-icon-144x144.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Props/client/public/img/icons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
3
Props/client/public/img/icons/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 215 B |
17
Props/client/public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<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="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
2
Props/client/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
76
Props/client/src/App.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div id="app-content" class="theme-light">
|
||||
<nav class="navbar" id="nav">
|
||||
<div class="container-fluid">
|
||||
<router-link class="navbar-brand" to="/">Props</router-link>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarContent"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
active-class="active"
|
||||
class="nav-link"
|
||||
to="/"
|
||||
>Home</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
active-class="active"
|
||||
class="nav-link"
|
||||
to="/about"
|
||||
>About</router-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<ProfileDisplay>
|
||||
</ProfileDisplay>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<ProfileSignUp>
|
||||
</ProfileSignUp>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<ProfileLogIn>
|
||||
</ProfileLogIn>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<ProfileLogOut>
|
||||
</ProfileLogOut>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "bootstrap/js/dist/collapse";
|
||||
import ProfileDisplay from "./components/ProfileDisplay.vue";
|
||||
import ProfileLogIn from "./components/ProfileLogIn.vue";
|
||||
import ProfileLogOut from "./components/ProfileLogOut.vue";
|
||||
import ProfileSignUp from "./components/ProfileSignUp.vue";
|
||||
export default {
|
||||
components: {
|
||||
ProfileDisplay,
|
||||
ProfileLogIn,
|
||||
ProfileSignUp,
|
||||
ProfileLogOut
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch("updatePublicApiSettings");
|
||||
this.$store.dispatch("loadUser");
|
||||
}
|
||||
};
|
||||
</script>
|
BIN
Props/client/src/assets/images/logo.png
Normal file
After Width: | Height: | Size: 21 KiB |
249
Props/client/src/assets/images/logo.svg
Normal file
@@ -0,0 +1,249 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="130.02815mm"
|
||||
height="183.73235mm"
|
||||
viewBox="0 0 130.02815 183.73235"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:export-filename="C:\Users\yunya\Documents\Props\Props\client\src\assets\images\logo.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="1.0607991"
|
||||
inkscape:cx="181.46697"
|
||||
inkscape:cy="340.78083"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-40.717182,-65.583996)">
|
||||
<rect
|
||||
style="fill:#51b6bf;fill-opacity:1;stroke:none;stroke-width:0.394384"
|
||||
id="rect846"
|
||||
width="130.02815"
|
||||
height="180.11604"
|
||||
x="40.717182"
|
||||
y="69.200294"
|
||||
ry="7.2278728" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.355205"
|
||||
id="rect6139"
|
||||
width="114.73653"
|
||||
height="165.58026"
|
||||
x="48.362995"
|
||||
y="76.468185"
|
||||
ry="6.6445661" />
|
||||
<g
|
||||
id="g20855"
|
||||
transform="translate(0,-4.5861139)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822-1"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="113.97274"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.86051;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136-7"
|
||||
width="42.169209"
|
||||
height="3.4690557"
|
||||
x="69.150803"
|
||||
y="116.29127"
|
||||
ry="1.7345278" />
|
||||
</g>
|
||||
<g
|
||||
id="g20859"
|
||||
transform="translate(0,-7.0555611)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822-14"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="127.76641"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.43107;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136-7-2"
|
||||
width="21.986813"
|
||||
height="3.9363635"
|
||||
x="68.953468"
|
||||
y="129.85129"
|
||||
ry="1.9681817" />
|
||||
</g>
|
||||
<g
|
||||
id="g20845"
|
||||
transform="translate(0,-2.1166667)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="100.17907"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.59414;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136"
|
||||
width="28.571259"
|
||||
height="3.7589138"
|
||||
x="69.028404"
|
||||
y="102.35267"
|
||||
ry="1.8794569" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.50408;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5136-5"
|
||||
width="24.788029"
|
||||
height="3.8569105"
|
||||
x="102.89133"
|
||||
y="102.30367"
|
||||
ry="1.9284552" />
|
||||
</g>
|
||||
<g
|
||||
id="g20863"
|
||||
transform="translate(0,-9.5250005)">
|
||||
<rect
|
||||
style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect4822-1-4"
|
||||
width="8.1061144"
|
||||
height="8.1061144"
|
||||
x="56.92775"
|
||||
y="141.56007"
|
||||
ry="1.3718038" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.57172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect5728"
|
||||
width="27.594185"
|
||||
height="3.7833092"
|
||||
x="69.018089"
|
||||
y="143.72148"
|
||||
ry="1.8916546" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#202042;fill-opacity:1;stroke:none;stroke-width:0.23824"
|
||||
id="rect1392"
|
||||
width="53.152565"
|
||||
height="18.233921"
|
||||
x="-132.30754"
|
||||
y="-83.817917"
|
||||
ry="3.4788733"
|
||||
transform="scale(-1)" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.22336;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6412"
|
||||
width="73.657707"
|
||||
height="2.8362327"
|
||||
x="56.336582"
|
||||
y="201.33577"
|
||||
ry="1.4181163" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.23876;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6414"
|
||||
width="69.712616"
|
||||
height="3.0383809"
|
||||
x="56.344284"
|
||||
y="209.24466"
|
||||
ry="1.5191904" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.42357;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6416"
|
||||
width="86.988022"
|
||||
height="2.8535743"
|
||||
x="56.436687"
|
||||
y="217.23825"
|
||||
ry="1.4267871" />
|
||||
<rect
|
||||
style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.38882;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6418"
|
||||
width="83.495445"
|
||||
height="2.8883159"
|
||||
x="56.419315"
|
||||
y="225.12207"
|
||||
ry="1.444158" />
|
||||
<rect
|
||||
style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:3.0459;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6295-8"
|
||||
width="57.490276"
|
||||
height="6.8199105"
|
||||
x="76.986122"
|
||||
y="87.608482"
|
||||
ry="3.4099553" />
|
||||
<rect
|
||||
style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:2.58155;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6490"
|
||||
width="41.297459"
|
||||
height="6.8199105"
|
||||
x="85.082527"
|
||||
y="188.09093"
|
||||
ry="3.4099553" />
|
||||
<rect
|
||||
style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:1.87563;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6490-3"
|
||||
width="21.8001"
|
||||
height="6.8199105"
|
||||
x="94.831207"
|
||||
y="151.63762"
|
||||
ry="3.4099553" />
|
||||
<rect
|
||||
style="opacity:1;fill:#2bc8d7;fill-opacity:1;stroke:none;stroke-width:1.63579;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6492"
|
||||
width="16.931116"
|
||||
height="13.580167"
|
||||
x="132.27409"
|
||||
y="201.23309"
|
||||
ry="2.6454868" />
|
||||
<rect
|
||||
style="opacity:1;fill:#dcf8f6;fill-opacity:1;stroke:none;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6932"
|
||||
width="79.717339"
|
||||
height="19.929335"
|
||||
x="65.872589"
|
||||
y="161.02196"
|
||||
ry="5.6437054" />
|
||||
<path
|
||||
style="fill:none;stroke:#3d39a9;stroke-width:1.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 67.636283,179.27583 17.812943,-8.46556 24.779394,3.96823 34.12678,-11.19923"
|
||||
id="path7699"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#6f78b2;stroke-width:0.765;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 65.872624,169.39934 37.918646,0.17637 17.98931,5.20279 23.63302,-7.14281"
|
||||
id="path8022" />
|
||||
<rect
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#b8edf3;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect9952"
|
||||
width="79.717339"
|
||||
height="19.929335"
|
||||
x="65.872589"
|
||||
y="161.02196"
|
||||
ry="5.6437054" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.4 KiB |
9
Props/client/src/assets/scss/_app-layout.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
// Used to provide inial layout for application containing elements.
|
||||
html, body, #app, #app-content {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#app-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
17
Props/client/src/assets/scss/_themer.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@use "sass:map";
|
||||
@use "variables";
|
||||
|
||||
// Applied Dmitry Borody's method for theming with modifications. - https://medium.com/@dmitriy.borodiy/easy-color-theming-with-scss-bc38fd5734d1
|
||||
@mixin themed($themes: variables.$themes) {
|
||||
@each $theme, $vars in $themes {
|
||||
.theme-#{$theme} &, &.theme-#{$theme} {
|
||||
$theme-values: $vars !global;
|
||||
@content;
|
||||
$theme-values: null !global;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@function color-of($type) {
|
||||
@return map.get($theme-values, $type);
|
||||
}
|
3
Props/client/src/assets/scss/_variables.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
$themes: (
|
||||
"light": ("background": #F4F4F4, "navbar": #FFF7F7, "main": #BDF2D5, "sub": #F2FCFC, "bold": #1E56A0, "text": #1A1A1A),
|
||||
);
|
48
Props/client/src/assets/scss/main.scss
Normal file
@@ -0,0 +1,48 @@
|
||||
@use "app-layout";
|
||||
@use "../../../node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
#app-content {
|
||||
@include themer.themed {
|
||||
background-color: themer.color-of("background");
|
||||
color: themer.color-of("text");
|
||||
}
|
||||
}
|
||||
|
||||
nav.navbar {
|
||||
@extend .navbar-expand-lg;
|
||||
@extend .sticky-top;
|
||||
@include themer.themed {
|
||||
@extend .navbar-light;
|
||||
background-color: themer.color-of("navbar");
|
||||
}
|
||||
}
|
||||
|
||||
.jumbotron {
|
||||
@extend .container-fluid;
|
||||
@extend .p-4;
|
||||
@include themer.themed {
|
||||
background-color: themer.color-of("main");
|
||||
}
|
||||
|
||||
&.sub {
|
||||
@include themer.themed {
|
||||
background-color: themer.color-of("sub");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 5em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
hr {
|
||||
@extend .my-3;
|
||||
@include themer.themed {
|
||||
color: themer.color-of("bold");
|
||||
}
|
||||
}
|
||||
|
19
Props/client/src/components/InfoCard.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><slot name="title">Title</slot></h5>
|
||||
<h6 class="card-subtitle mb-3 text-muted">
|
||||
<slot name="subtitle">Subtitle</slot>
|
||||
</h6>
|
||||
<p class="card-text">
|
||||
<slot name="text"> </slot>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "InfoCard"
|
||||
};
|
||||
</script>
|
53
Props/client/src/components/ProfileDisplay.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<a v-if="visible" :href="manageUrl" class="btn">
|
||||
<slot v-if="username" :displayName="username" name="username">
|
||||
{{ username }}
|
||||
</slot>
|
||||
<slot v-else-if="isProfileLoading" name="loading">
|
||||
<WaitCircle class="spinner-grow-sm"></WaitCircle>
|
||||
</slot>
|
||||
<slot v-else name="unauthenticated">
|
||||
<span>Not Logged In</span>
|
||||
</slot>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { identityPaths } from "../services/authentication";
|
||||
import WaitCircle from "./WaitCircle.vue";
|
||||
export default {
|
||||
components: {
|
||||
WaitCircle
|
||||
},
|
||||
name: "ProfileDisplay",
|
||||
props: {
|
||||
showUnauthenticated: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
manageUrl: identityPaths.Manage,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
username() {
|
||||
return this.$store.getters.username &&
|
||||
!this.$store.getters.isIdentityLoading
|
||||
? this.$store.getters.username
|
||||
: null;
|
||||
},
|
||||
visible() {
|
||||
return !this.showUnauthenticated
|
||||
? this.$store.getters.isAuthenticated ||
|
||||
this.$store.getters.isIdentityLoading
|
||||
: true;
|
||||
},
|
||||
isProfileLoading() {
|
||||
return this.$store.getters.isIdentityLoading;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
21
Props/client/src/components/ProfileLogIn.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<button v-if="visible" @click="onClick" type="button" class="btn">
|
||||
<slot>Log In</slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ProfileLogIn",
|
||||
computed: {
|
||||
visible() {
|
||||
return !this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$store.dispatch("beginAuthentication");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
21
Props/client/src/components/ProfileLogOut.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<button v-if="visible" @click="onClick" type="button" class="btn">
|
||||
<slot>Log Out</slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ProfileLogOut",
|
||||
computed: {
|
||||
visible() {
|
||||
return this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$store.dispatch("beginDeauthentication");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
21
Props/client/src/components/ProfileSignUp.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<button v-if="visible" @click="onClick" type="button" class="btn">
|
||||
<slot>Sign Up!</slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ProfileSignUp",
|
||||
computed: {
|
||||
visible() {
|
||||
return this.$store.state.identity.registrationEnabled && !this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$store.dispatch("beginRegistration");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
39
Props/client/src/components/SearchBar.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<form autocomplete="off">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" :placeholder="placeholder" v-model="query" @keyup.enter="onEnter">
|
||||
<slot name="append"></slot>
|
||||
<button class="btn btn-outline-primary" type="button" @click="onClick">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
placeholder: String,
|
||||
searchCallback: Function
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: "",
|
||||
disabled: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.disabled = true;
|
||||
this.searchCallback(this.query);
|
||||
this.disabled = false;
|
||||
},
|
||||
onEnter() {
|
||||
this.disabled = true;
|
||||
this.searchCallback(this.query);
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
24
Props/client/src/components/WaitCircle.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mx-auto" id="spinner-container">
|
||||
<div class="spinner-grow" role="status" v-bind="$attrs">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#spinner-container {
|
||||
width: fit-content;
|
||||
}
|
||||
</style>
|
15
Props/client/src/main.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
|
||||
import "../node_modules/bootstrap-icons/font/bootstrap-icons.css";
|
||||
import "./assets/scss/main.scss"; // Main global scss file.
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
|
||||
app.mount("#app");
|
32
Props/client/src/registerServiceWorker.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from "register-service-worker";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
console.log(
|
||||
"App is being served from cache by a service worker.\n" +
|
||||
"For more details, visit https://goo.gl/AFskqB"
|
||||
);
|
||||
},
|
||||
registered() {
|
||||
console.log("Service worker has been registered.");
|
||||
},
|
||||
cached() {
|
||||
console.log("Content has been cached for offline use.");
|
||||
},
|
||||
updatefound() {
|
||||
console.log("New content is downloading.");
|
||||
},
|
||||
updated() {
|
||||
console.log("New content is available; please refresh.");
|
||||
},
|
||||
offline() {
|
||||
console.log("No internet connection found. App is running in offline mode.");
|
||||
},
|
||||
error(error) {
|
||||
console.error("Error during service worker registration:", error);
|
||||
}
|
||||
});
|
||||
}
|
13
Props/client/src/router/guards.js
Normal file
@@ -0,0 +1,13 @@
|
||||
function authenticationGuard(app, to, from, next) {
|
||||
if (to.authenticationRequired) {
|
||||
if (app.$store.getters.identity.isAuthenticated) {
|
||||
next();
|
||||
} else {
|
||||
app.$store.dispatch("beginAuthentication");
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
export { authenticationGuard };
|
11
Props/client/src/router/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import { authenticationGuard } from "./guards";
|
||||
import { routes } from "./routes";
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => authenticationGuard(router, to, from, next));
|
||||
|
||||
export default router;
|
25
Props/client/src/router/routes.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import Home from "../views/Home.vue";
|
||||
import Authentication from "../views/Authentication.vue";
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "About",
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import(/* webpackChunkName: "about" */ "../views/About.vue")
|
||||
},
|
||||
{
|
||||
path: "/authentication/:action?",
|
||||
name: "Authentication",
|
||||
component: Authentication,
|
||||
props: true,
|
||||
}
|
||||
];
|
||||
|
||||
export { routes };
|
22
Props/client/src/services/authentication.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { UserManager, WebStorageStateStore } from "oidc-client";
|
||||
|
||||
const userManager = new UserManager({
|
||||
authority: window.location.origin,
|
||||
client_id: "Props",
|
||||
redirect_uri: window.location.origin + "/authentication/login-callback",
|
||||
post_logout_redirect_uri: window.location.origin + "/authentication/logout-callback",
|
||||
response_type: "code",
|
||||
scope: "openid profile",
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
});
|
||||
|
||||
const identityPaths = {
|
||||
Manage: "Identity/Account/Manage",
|
||||
Register: "/Identity/Account/Register"
|
||||
};
|
||||
|
||||
const identityQueryParameters = {
|
||||
ReturnUrl: "returnUrl",
|
||||
};
|
||||
|
||||
export { userManager, identityPaths, identityQueryParameters };
|
23
Props/client/src/services/http.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import axios from "axios";
|
||||
|
||||
let currentAuthorizationInterceptorID = null;
|
||||
|
||||
const apiHttp = axios.create({
|
||||
baseURL: window.location.origin + "/api",
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
function addBearerTokenInterceptor(token) {
|
||||
currentAuthorizationInterceptorID = apiHttp.interceptors.request.use((config) => {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
return config;
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
function removeBearerTokenInterceptor() {
|
||||
apiHttp.interceptors.request.eject(currentAuthorizationInterceptorID);
|
||||
}
|
||||
|
||||
export { apiHttp, addBearerTokenInterceptor, removeBearerTokenInterceptor };
|
31
Props/client/src/services/persistence.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const prefix = "Props";
|
||||
function put(key, value) {
|
||||
if (value == null) return false;
|
||||
try {
|
||||
localStorage.setItem(prefix + ":" + key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function get(key) {
|
||||
if (!exists(key)) return null;
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(prefix + ":" + key));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function remove(key) {
|
||||
localStorage.removeItem(prefix + ":" + key);
|
||||
}
|
||||
|
||||
function exists(key) {
|
||||
return localStorage.getItem(prefix + ":" + key) != null;
|
||||
}
|
||||
|
||||
export { put, get, remove, exists };
|
132
Props/client/src/store/identity.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import router from "../router";
|
||||
import { identityPaths, identityQueryParameters, userManager } from "../services/authentication";
|
||||
import { addBearerTokenInterceptor, apiHttp, removeBearerTokenInterceptor } from "../services/http";
|
||||
import { get, put } from "../services/persistence";
|
||||
|
||||
const identity = {
|
||||
state: () => ({
|
||||
user: null,
|
||||
loadingLayers: 0,
|
||||
callbackPath: null,
|
||||
registrationEnabled: false,
|
||||
}),
|
||||
getters: {
|
||||
isAuthenticated(state) {
|
||||
return state.user ? !state.user.expired : false;
|
||||
},
|
||||
isIdentityLoading(state) {
|
||||
return state.loadingLayers > 0;
|
||||
},
|
||||
username(state) {
|
||||
return (state.user && state.user.profile.name) ? state.user.profile.name : null;
|
||||
},
|
||||
authCallbackLocation(state) {
|
||||
return state.callbackPath ? state.callbackPath : get("callbackPath");
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
login(state, { user }) {
|
||||
if (!user) return;
|
||||
state.user = user;
|
||||
addBearerTokenInterceptor(user.access_token);
|
||||
},
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
removeBearerTokenInterceptor();
|
||||
},
|
||||
beginAuthenticating(state) {
|
||||
state.loading += 1;
|
||||
},
|
||||
endAuthenticating(state) {
|
||||
state.loading -= 1;
|
||||
},
|
||||
authCallbackLocation(state) {
|
||||
state.callbackPath = window.location.pathname;
|
||||
put("callbackPath", state.callbackPath);
|
||||
},
|
||||
enableIdentificationRegistration(state) {
|
||||
state.registrationEnabled = true;
|
||||
},
|
||||
disableIdentificationRegistration(state) {
|
||||
state.registrationEnabled = false;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async loadUser(context) {
|
||||
context.commit("beginAuthenticating");
|
||||
const user = await userManager.getUser();
|
||||
if (user != null) {
|
||||
context.commit("login", { user });
|
||||
}
|
||||
context.commit("endAuthenticating");
|
||||
},
|
||||
async attemptSilentAuthentication(context, options) {
|
||||
if (context.getters.isAuthenticated) return;
|
||||
context.commit("beginAuthenticating");
|
||||
context.dispatch("loadUser");
|
||||
if (!context.getters.isAuthenticated) {
|
||||
try {
|
||||
const user = await userManager.signinSilent({
|
||||
redirect_uri: window.location.origin + "/authentication/silent-login-callback.html"
|
||||
});
|
||||
|
||||
context.commit("login", { user });
|
||||
} catch { }
|
||||
context.commit("endAuthenticating");
|
||||
}
|
||||
|
||||
if (options && options.redirect) {
|
||||
router.replace(context.getters.authCallbackLocation);
|
||||
}
|
||||
},
|
||||
async beginAuthentication(context) {
|
||||
if (context.getters.isAuthenticated) return;
|
||||
context.commit("beginAuthenticating");
|
||||
context.dispatch("loadUser");
|
||||
if (!context.getters.isAuthenticated) {
|
||||
context.commit("authCallbackLocation");
|
||||
userManager.signinRedirect();
|
||||
}
|
||||
},
|
||||
async beginDeauthentication(context) {
|
||||
if (!context.getters.isAuthenticated) return;
|
||||
context.commit("beginAuthenticating");
|
||||
context.commit("authCallbackLocation");
|
||||
userManager.signoutRedirect();
|
||||
},
|
||||
async completeAuthentication(context, { user }) {
|
||||
if (!user) return;
|
||||
context.commit("login", { user });
|
||||
context.commit("endAuthenticating");
|
||||
router.replace(context.getters.authCallbackLocation);
|
||||
},
|
||||
async completeDeauthentication(context) {
|
||||
context.commit("logout");
|
||||
try {
|
||||
await userManager.removeUser();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
context.commit("endAuthenticating");
|
||||
router.replace(context.getters.authCallbackLocation);
|
||||
},
|
||||
beginRegistration(context) {
|
||||
context.commit("authCallbackLocation");
|
||||
window.location.replace(window.location.origin + identityPaths.Register + "?" + identityQueryParameters.ReturnUrl + "=" + "/authentication/silent-login");
|
||||
},
|
||||
async updatePublicApiSettings(context) {
|
||||
try {
|
||||
const settings = (await apiHttp.get("PublicApiSettings")).data;
|
||||
if (settings.RegistrationEnabled === "True") {
|
||||
context.commit("enableIdentificationRegistration");
|
||||
} else {
|
||||
context.commit("disableIdentificationRegistration");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export { identity };
|
16
Props/client/src/store/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createStore } from "vuex";
|
||||
import { identity } from "./identity";
|
||||
|
||||
export default createStore({
|
||||
state: {
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
identity: identity,
|
||||
},
|
||||
});
|
5
Props/client/src/views/About.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
69
Props/client/src/views/Authentication.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="info">
|
||||
<WaitCircle class="text-primary">
|
||||
<span class="my-1 text-muted"
|
||||
>Hang on just a sec! Waiting for authentication stuff to
|
||||
finish.</span
|
||||
>
|
||||
</WaitCircle>
|
||||
<div class="my-1 text-muted" v-show="longTimePassed">
|
||||
This seems to be taking longer than expected...
|
||||
<router-link to="/">Return home</router-link>?
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaitCircle from "../components/WaitCircle.vue";
|
||||
import { userManager } from "../services/authentication";
|
||||
|
||||
export default {
|
||||
name: "Authentication",
|
||||
props: {
|
||||
action: String
|
||||
},
|
||||
components: {
|
||||
WaitCircle
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
longTimePassed: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => (this.longTimePassed = true), 5000);
|
||||
this.completeCallback();
|
||||
},
|
||||
methods: {
|
||||
async completeCallback() {
|
||||
console.log("Completing callback for " + this.action);
|
||||
if (this.action === "login-callback") {
|
||||
const user = await userManager.signinRedirectCallback();
|
||||
this.$store.dispatch("completeAuthentication", { user });
|
||||
} else if (this.action === "logout-callback") {
|
||||
await userManager.signoutRedirectCallback();
|
||||
this.$store.dispatch("completeDeauthentication");
|
||||
} else if (this.action === "silent-login") {
|
||||
this.$store.dispatch("attemptSilentAuthentication", { redirect: true });
|
||||
} else {
|
||||
console.warn("Unknown callback: " + this.action);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async action() {
|
||||
this.completeCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
156
Props/client/src/views/Home.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div id="main" class="jumbotron d-flex flex-column align-items-center">
|
||||
<div>
|
||||
<img
|
||||
id="large-logo"
|
||||
alt="SHAID logo"
|
||||
src="../assets/images/logo.svg"
|
||||
class="img-fluid"
|
||||
/>
|
||||
</div>
|
||||
<div id="description" class="text-center px-3 my-2">
|
||||
<h1 class="my-2">Props</h1>
|
||||
<p>
|
||||
{{ content?.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jumbotron sub">
|
||||
<div
|
||||
id="help"
|
||||
class="container d-flex flex-column align-items-center my-3"
|
||||
>
|
||||
<div class="large"><i class="bi bi-search"></i></div>
|
||||
<h2 class="mb-3 mt-4">{{ content?.help.title }}</h2>
|
||||
<SearchBar
|
||||
id="searchbar"
|
||||
placeholder="Search here to get started!"
|
||||
class="my-4"
|
||||
style="width: 100%;"
|
||||
></SearchBar>
|
||||
<p>
|
||||
{{ content?.help.searchIntroduction }}
|
||||
</p>
|
||||
<p>
|
||||
{{ content?.help.additionalInfo }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="features"
|
||||
class="container d-flex flex-column align-items-center my-3"
|
||||
>
|
||||
<h2 class="mb-3 mt-4">Features</h2>
|
||||
<p>
|
||||
{{ content?.features.description }}
|
||||
</p>
|
||||
<div class="overflow-auto">
|
||||
<div v-if="content" class="row my-2">
|
||||
<InfoCard
|
||||
v-for="feature in content.features.list"
|
||||
v-bind:key="feature"
|
||||
style="width: 32rem"
|
||||
>
|
||||
<template v-slot:title>{{ feature.title }}</template>
|
||||
<template v-slot:subtitle>{{ feature.subtitle }}</template>
|
||||
<template v-slot:text>{{ feature.text }}</template>
|
||||
</InfoCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InfoCard from "../components/InfoCard.vue";
|
||||
import SearchBar from "../components/SearchBar.vue";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
InfoCard,
|
||||
SearchBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
content: {
|
||||
description:
|
||||
"Props is a site designed to help with the online project component shopping experience. Create project component lists and search across multiple commonly used online retail stores to find the ideal purchase.",
|
||||
help: {
|
||||
title: "Getting Started",
|
||||
searchIntroduction:
|
||||
"Props is a site designed to help with the online project component shopping experience. Create project component lists and search across multiple commonly used online retail stores to find the ideal purchase.",
|
||||
additionalInfo:
|
||||
"Take advantage of our project component manager, where you can add detailed descriptions of what things are for, as well as overall project summaries."
|
||||
},
|
||||
features: {
|
||||
description:
|
||||
"Props strives to be a platform where people can find and organize the material they need to complete their projects. Our features are therefore tailored to the community helping each other, as well as smart tools to quickly compare different products from different stores.",
|
||||
list: [
|
||||
{
|
||||
title: "Shopping List",
|
||||
subtitle: "We'll do it so you don't need to.",
|
||||
text:
|
||||
"We'll help you track the components you need. You can add descriptions, and mark things as purchased, or shipped at your convenience. You can even add components from stores that Props doesn't know about yet!"
|
||||
},
|
||||
{
|
||||
title: "Product Comparison",
|
||||
subtitle: "So many shops to look through...",
|
||||
text:
|
||||
"There's so many online retailers nowadays that it's becoming more and more work to check all of them for what you need. With us, we'll do the searching for you. All you need to do is decide if the shipping time, cost, ratings and reviews are suitable!"
|
||||
},
|
||||
{
|
||||
title: "Auto Listing Search",
|
||||
subtitle: "Need a starting point?",
|
||||
text:
|
||||
"Some project parts commonly used. We'll try and find these parts for you automatically. This means you can create a shopping list, and we'll try and the best fitting products for you based on your shopping list entries."
|
||||
},
|
||||
{
|
||||
title: "Share It!",
|
||||
subtitle: "Show off your work!",
|
||||
text:
|
||||
"Have a project that you're excited about? Want to tell all your friends? We'll help. Each of your projects has a privacy setting. You can have it publically listed, unlisted, or completely private. Short link included."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#large-logo {
|
||||
max-height: 270px;
|
||||
}
|
||||
|
||||
.overflow-auto {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-wrap: nowrap;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
#help {
|
||||
max-width: 810px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#description {
|
||||
max-width: 540px;
|
||||
}
|
||||
|
||||
.large {
|
||||
.bi {
|
||||
font-size: 5em;
|
||||
}
|
||||
}
|
||||
</style>
|
10
Props/client/tests/e2e/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
plugins: ['wdio'],
|
||||
extends: 'plugin:wdio/recommended',
|
||||
env: {
|
||||
mocha: true
|
||||
},
|
||||
rules: {
|
||||
strict: 'off'
|
||||
}
|
||||
}
|
15
Props/client/tests/e2e/pageobjects/app.page.js
Normal file
@@ -0,0 +1,15 @@
|
||||
class App {
|
||||
/**
|
||||
* elements
|
||||
*/
|
||||
get heading () { return $('h1') }
|
||||
|
||||
/**
|
||||
* methods
|
||||
*/
|
||||
open (path = '/') {
|
||||
browser.url(path)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new App()
|
8
Props/client/tests/e2e/specs/app.spec.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const App = require('../pageobjects/app.page')
|
||||
|
||||
describe('Vue.js app', () => {
|
||||
it('should open and render', () => {
|
||||
App.open()
|
||||
expect(App.heading).toHaveText('Welcome to Your Vue.js App')
|
||||
})
|
||||
})
|
13
Props/client/tests/unit/example.spec.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { expect } from 'chai'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
|
||||
describe('HelloWorld.vue', () => {
|
||||
it('renders props.msg when passed', () => {
|
||||
const msg = 'new message'
|
||||
const wrapper = shallowMount(HelloWorld, {
|
||||
props: { msg }
|
||||
})
|
||||
expect(wrapper.text()).to.include(msg)
|
||||
})
|
||||
})
|
18
Props/client/vue.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
pages: {
|
||||
index: {
|
||||
entry: "src/main.js",
|
||||
template: "public/index.html",
|
||||
title: "Props"
|
||||
}
|
||||
},
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
prependData: `
|
||||
@use "@/assets/scss/_themer.scss";
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
24
Props/client/wdio.local.conf.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { config } = require('./wdio.shared.conf')
|
||||
|
||||
exports.config = {
|
||||
/**
|
||||
* base config
|
||||
*/
|
||||
...config,
|
||||
/**
|
||||
* config for local testing
|
||||
*/
|
||||
maxInstances: 1,
|
||||
services: ['chromedriver'],
|
||||
capabilities: [
|
||||
{
|
||||
browserName: 'chrome',
|
||||
acceptInsecureCerts: true,
|
||||
'goog:chromeOptions': {
|
||||
args: process.argv.includes('--headless')
|
||||
? ['--headless', '--disable-gpu']
|
||||
: []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
41
Props/client/wdio.sauce.conf.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { config } = require('./wdio.shared.conf')
|
||||
|
||||
const BUILD_ID = Math.ceil(Date.now() / 1000)
|
||||
|
||||
exports.config = {
|
||||
/**
|
||||
* base config
|
||||
*/
|
||||
...config,
|
||||
/**
|
||||
* config for testing on Sauce Labs
|
||||
*/
|
||||
user: process.env.SAUCE_USERNAME,
|
||||
key: process.env.SAUCE_ACCESS_KEY,
|
||||
region: 'us',
|
||||
headless: process.argv.includes('--headless'),
|
||||
|
||||
services: [
|
||||
['sauce', {
|
||||
sauceConnect: true,
|
||||
tunnelIdentifier: 'Vue.js Integration tests'
|
||||
}]
|
||||
],
|
||||
|
||||
maxInstances: 10,
|
||||
capabilities: [{
|
||||
browserName: 'firefox',
|
||||
browserVersion: 'latest',
|
||||
platformName: 'Windows 10',
|
||||
'sauce:options': {
|
||||
build: `Build ${BUILD_ID}`
|
||||
}
|
||||
}, {
|
||||
browserName: 'chrome',
|
||||
browserVersion: 'latest',
|
||||
platformName: 'Windows 10',
|
||||
'sauce:options': {
|
||||
build: `Build ${BUILD_ID}`
|
||||
}
|
||||
}]
|
||||
}
|
210
Props/client/wdio.shared.conf.js
Normal file
@@ -0,0 +1,210 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
const path = require('path')
|
||||
|
||||
exports.config = {
|
||||
// ==================
|
||||
// Specify Test Files
|
||||
// ==================
|
||||
// Define which test specs should run. The pattern is relative to the directory
|
||||
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
|
||||
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
|
||||
// directory is where your package.json resides, so `wdio` will be called from there.
|
||||
//
|
||||
specs: [
|
||||
path.join(__dirname, '/tests/e2e/**/*.spec.js')
|
||||
],
|
||||
// Patterns to exclude.
|
||||
exclude: [
|
||||
// 'test/spec/multibrowser/**',
|
||||
// 'test/spec/mobile/**'
|
||||
],
|
||||
//
|
||||
// ===================
|
||||
// Test Configurations
|
||||
// ===================
|
||||
// Define all options that are relevant for the WebdriverIO instance here
|
||||
//
|
||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||
logLevel: 'trace',
|
||||
//
|
||||
// Set directory to store all logs into
|
||||
outputDir: path.join(__dirname, 'tests/e2e/logs'),
|
||||
//
|
||||
// If you only want to run your tests until a specific amount of tests have failed use
|
||||
// bail (default is 0 - don't bail, run all tests).
|
||||
bail: 0,
|
||||
//
|
||||
// Default timeout for all waitFor* commands.
|
||||
waitforTimeout: 1000,
|
||||
//
|
||||
// Framework you want to run your specs with.
|
||||
// The following are supported: Mocha, Jasmine, and Cucumber
|
||||
// see also: https://webdriver.io/docs/frameworks.html
|
||||
//
|
||||
// Make sure you have the wdio adapter package for the specific framework
|
||||
// installed before running any tests.
|
||||
framework: 'mocha',
|
||||
//
|
||||
// The number of times to retry the entire specfile when it fails as a whole
|
||||
specFileRetries: 1,
|
||||
//
|
||||
// Retried specfiles are inserted at the beginning of the queue and retried immediately
|
||||
specFileRetriesDeferred: false,
|
||||
//
|
||||
// Test reporter for stdout.
|
||||
// The only one supported by default is 'dot'
|
||||
// see also: https://webdriver.io/docs/dot-reporter.html
|
||||
reporters: ['spec'],
|
||||
//
|
||||
// Options to be passed to Mocha.
|
||||
// See the full list at http://mochajs.org/
|
||||
mochaOpts: {
|
||||
ui: 'bdd',
|
||||
timeout: 30000
|
||||
},
|
||||
//
|
||||
// =====
|
||||
// Hooks
|
||||
// =====
|
||||
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
|
||||
// it and to build services around it. You can either apply a single function or an array of
|
||||
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
|
||||
// resolved to continue.
|
||||
//
|
||||
/**
|
||||
* Gets executed once before all workers get launched.
|
||||
* @param {Object} config wdio configuration object
|
||||
* @param {Array.<Object>} capabilities list of capabilities details
|
||||
*/
|
||||
onPrepare: function (config, capabilities) {
|
||||
},
|
||||
/**
|
||||
* Gets executed before a worker process is spawned and can be used to initialise specific service
|
||||
* for that worker as well as modify runtime environments in an async fashion.
|
||||
* @param {String} cid capability id (e.g 0-0)
|
||||
* @param {[type]} caps object containing capabilities for session that will be spawn in the worker
|
||||
* @param {[type]} specs specs to be run in the worker process
|
||||
* @param {[type]} args object that will be merged with the main configuration once worker is initialised
|
||||
* @param {[type]} execArgv list of string arguments passed to the worker process
|
||||
*/
|
||||
onWorkerStart: function (cid, caps, specs, args, execArgv) {
|
||||
},
|
||||
/**
|
||||
* Gets executed just before initialising the webdriver session and test framework. It allows you
|
||||
* to manipulate configurations depending on the capability or spec.
|
||||
* @param {Object} config wdio configuration object
|
||||
* @param {Array.<Object>} capabilities list of capabilities details
|
||||
* @param {Array.<String>} specs List of spec file paths that are to be run
|
||||
*/
|
||||
beforeSession: function (config, capabilities, specs) {
|
||||
},
|
||||
/**
|
||||
* Gets executed before test execution begins. At this point you can access to all global
|
||||
* variables like `browser`. It is the perfect place to define custom commands.
|
||||
* @param {Array.<Object>} capabilities list of capabilities details
|
||||
* @param {Array.<String>} specs List of spec file paths that are to be run
|
||||
*/
|
||||
before: function (capabilities, specs) {
|
||||
},
|
||||
/**
|
||||
* Hook that gets executed before the suite starts
|
||||
* @param {Object} suite suite details
|
||||
*/
|
||||
beforeSuite: function (suite) {
|
||||
},
|
||||
/**
|
||||
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
|
||||
* beforeEach in Mocha)
|
||||
* stepData and world are Cucumber framework specific
|
||||
*/
|
||||
beforeHook: function (test, context/*, stepData, world */) {
|
||||
},
|
||||
/**
|
||||
* Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
|
||||
* afterEach in Mocha)
|
||||
* stepData and world are Cucumber framework specific
|
||||
*/
|
||||
afterHook: function (test, context, { error, result, duration, passed, retries }/*, stepData, world */) {
|
||||
},
|
||||
/**
|
||||
* Function to be executed before a test (in Mocha/Jasmine) starts.
|
||||
*/
|
||||
beforeTest: function (test, context) {
|
||||
},
|
||||
//
|
||||
/**
|
||||
* Runs before a WebdriverIO command gets executed.
|
||||
* @param {String} commandName command name
|
||||
* @param {Array} args arguments that command would receive
|
||||
*/
|
||||
beforeCommand: function (commandName, args) {
|
||||
},
|
||||
/**
|
||||
* Runs after a WebdriverIO command gets executed.
|
||||
* @param {String} commandName hook command name
|
||||
* @param {Array} args arguments that command would receive
|
||||
* @param {Number} result 0 - command success, 1 - command error
|
||||
* @param {Object} error error object if any
|
||||
*/
|
||||
afterCommand: function (commandName, args, result, error) {
|
||||
},
|
||||
/**
|
||||
* Function to be executed after a test (in Mocha/Jasmine) ends.
|
||||
*/
|
||||
afterTest: function (test, context, { error, result, duration, passed, retries }) {
|
||||
},
|
||||
/**
|
||||
* Hook that gets executed after the suite has ended
|
||||
* @param {Object} suite suite details
|
||||
*/
|
||||
afterSuite: function (suite) {
|
||||
},
|
||||
/**
|
||||
* Gets executed after all tests are done. You still have access to all global variables from
|
||||
* the test.
|
||||
* @param {Number} result 0 - test pass, 1 - test fail
|
||||
* @param {Array.<Object>} capabilities list of capabilities details
|
||||
* @param {Array.<String>} specs List of spec file paths that ran
|
||||
*/
|
||||
after: function (result, capabilities, specs) {
|
||||
},
|
||||
/**
|
||||
* Gets executed right after terminating the webdriver session.
|
||||
* @param {Object} config wdio configuration object
|
||||
* @param {Array.<Object>} capabilities list of capabilities details
|
||||
* @param {Array.<String>} specs List of spec file paths that ran
|
||||
*/
|
||||
afterSession: function (config, capabilities, specs) {
|
||||
},
|
||||
/**
|
||||
* Gets executed after all workers got shut down and the process is about to exit. An error
|
||||
* thrown in the onComplete hook will result in the test run failing.
|
||||
* @param {Object} exitCode 0 - success, 1 - fail
|
||||
* @param {Object} config wdio configuration object
|
||||
* @param {Array.<Object>} capabilities list of capabilities details
|
||||
* @param {<Object>} results object containing test results
|
||||
*/
|
||||
onComplete: function (exitCode, config, capabilities, results) {
|
||||
},
|
||||
/**
|
||||
* Gets executed when a refresh happens.
|
||||
* @param {String} oldSessionId session ID of the old session
|
||||
* @param {String} newSessionId session ID of the new session
|
||||
*/
|
||||
onReload: function (oldSessionId, newSessionId) {
|
||||
},
|
||||
//
|
||||
// Cucumber specific hooks
|
||||
beforeFeature: function (uri, feature, scenarios) {
|
||||
},
|
||||
beforeScenario: function (uri, feature, scenario, sourceLocation) {
|
||||
},
|
||||
beforeStep: function ({ uri, feature, step }, context) {
|
||||
},
|
||||
afterStep: function ({ uri, feature, step }, context, { error, result, duration, passed, retries }) {
|
||||
},
|
||||
afterScenario: function (uri, feature, scenario, result, sourceLocation) {
|
||||
},
|
||||
afterFeature: function (uri, feature, scenarios) {
|
||||
}
|
||||
}
|
28
Props/scripts/reset_db.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
SERVER_DIR = "server"
|
||||
DATA_DIR = "Data"
|
||||
DB_MIGRATE_CMD = "dotnet ef migrations add InitialCreate -o {0}"
|
||||
DB_UPDATE_CMD = "dotnet ef database update"
|
||||
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
os.chdir("..")
|
||||
os.chdir(SERVER_DIR)
|
||||
print("Working in: " + os.getcwd())
|
||||
|
||||
migrationsDir = os.path.join(DATA_DIR, "Migrations")
|
||||
|
||||
print("Deleting current migrations directory if it exists.")
|
||||
shutil.rmtree(migrationsDir, ignore_errors=True)
|
||||
|
||||
print("Deleting old app.db if it exists.")
|
||||
if os.path.exists("app.db"):
|
||||
os.remove("app.db")
|
||||
|
||||
print("Creating migration.")
|
||||
os.system(DB_MIGRATE_CMD.format(migrationsDir))
|
||||
|
||||
print("Updating database.")
|
||||
os.system(DB_UPDATE_CMD)
|
31
Props/scripts/watch_all.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
SERVER_CSPROJ_DIR = "server"
|
||||
CLIENT_PACKAGE_DIR = "client"
|
||||
|
||||
|
||||
async def exec(cmd, path, silent=False):
|
||||
devnull = open(os.devnull, "wb")
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
os.chdir(os.pardir)
|
||||
os.chdir(path)
|
||||
proc = None
|
||||
if (not silent):
|
||||
print("Executing \"{0}\" in \"{1}\".".format(cmd, path))
|
||||
proc = await asyncio.create_subprocess_shell(cmd)
|
||||
else:
|
||||
print("Executing \"{0}\" in \"{1}\" silently.".format(cmd, path))
|
||||
proc = await asyncio.create_subprocess_shell(cmd, stdout=devnull)
|
||||
|
||||
await proc.wait()
|
||||
devnull.close()
|
||||
|
||||
|
||||
async def main():
|
||||
print("Beginning development servers.")
|
||||
await asyncio.gather(
|
||||
exec("dotnet watch run", SERVER_CSPROJ_DIR),
|
||||
exec("npm run serve", CLIENT_PACKAGE_DIR, silent=True))
|
||||
|
||||
asyncio.run(main())
|
231
Props/server/.gitignore
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
bin/
|
||||
Bin/
|
||||
obj/
|
||||
Obj/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# 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
|
||||
# TODO: 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
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Microsoft Azure ApplicationInsights config file
|
||||
ApplicationInsights.config
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
|
||||
# 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
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
/node_modules
|
||||
|
||||
# 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
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# 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
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
26
Props/server/Controllers/OidcConfigurationController.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Props.Controllers
|
||||
{
|
||||
public class OidcConfigurationController : Controller
|
||||
{
|
||||
private readonly ILogger<OidcConfigurationController> _logger;
|
||||
|
||||
public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> logger)
|
||||
{
|
||||
ClientRequestParametersProvider = clientRequestParametersProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IClientRequestParametersProvider ClientRequestParametersProvider { get; }
|
||||
|
||||
[HttpGet("_configuration/{clientId}")]
|
||||
public IActionResult GetClientRequestParameters([FromRoute]string clientId)
|
||||
{
|
||||
var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);
|
||||
return Ok(parameters);
|
||||
}
|
||||
}
|
||||
}
|
32
Props/server/Controllers/PublicApiSettingsController.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Props.Options;
|
||||
|
||||
namespace Props.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PublicApiSettingsController : ControllerBase
|
||||
{
|
||||
private IConfiguration configuration;
|
||||
public PublicApiSettingsController(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Get() {
|
||||
return Ok(BuildPublicApiSettings());
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, string> BuildPublicApiSettings() {
|
||||
IdentificationOptions identificationOptions = configuration.GetSection(IdentificationOptions.Identification).Get<IdentificationOptions>();
|
||||
return new Dictionary<string,string>() {
|
||||
// Build dictionary containing options client should be aware of.
|
||||
{"RegistrationEnabled", identificationOptions.RegistrationEnabled.ToString()}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
82
Props/server/Controllers/UserConfigurationsController.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Props.Data;
|
||||
using Props.Models;
|
||||
using Props.Shared.Models;
|
||||
|
||||
namespace Props.Server.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
public class UserConfigurationsController : ControllerBase
|
||||
{
|
||||
private ILogger<UserConfigurationsController> logger;
|
||||
private UserManager<ApplicationUser> userManager;
|
||||
private ApplicationDbContext dbContext;
|
||||
public UserConfigurationsController(UserManager<ApplicationUser> userManager, ApplicationDbContext dbContext, ILogger<UserConfigurationsController> logger)
|
||||
{
|
||||
this.userManager = userManager;
|
||||
this.dbContext = dbContext;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetSearchOutline() {
|
||||
ApplicationUser userModel = await userManager.GetUserAsync(User);
|
||||
return Ok(userModel.SearchOutline);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetResultsPreferences() {
|
||||
ApplicationUser userModel = await userManager.GetUserAsync(User);
|
||||
return Ok(userModel.ResultsPreferences);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetApplicationPreferences() {
|
||||
ApplicationUser userModel = await userManager.GetUserAsync(User);
|
||||
logger.LogInformation(JsonSerializer.Serialize(userModel.ApplicationPreferences));
|
||||
return Ok(userModel.ApplicationPreferences);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutSearchOutline(SearchOutline searchOutline) {
|
||||
ApplicationUser userModel = await userManager.GetUserAsync(User);
|
||||
if (userModel.SearchOutline.Id != searchOutline.Id || userModel.Id != searchOutline.ApplicationUserId) {
|
||||
return BadRequest();
|
||||
}
|
||||
dbContext.Entry(userModel.SearchOutline).CurrentValues.SetValues(searchOutline);
|
||||
await userManager.UpdateAsync(userModel);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutResultsPreferences(ResultsPreferences resultsPreferences) {
|
||||
ApplicationUser userModel = await userManager.GetUserAsync(User);
|
||||
if (userModel.ResultsPreferences.Id != resultsPreferences.Id || userModel.Id != resultsPreferences.ApplicationUserId) {
|
||||
return BadRequest();
|
||||
}
|
||||
dbContext.Entry(userModel.ResultsPreferences).CurrentValues.SetValues(resultsPreferences);
|
||||
await userManager.UpdateAsync(userModel);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutApplicationPreferences(ApplicationPreferences applicationPreferences) {
|
||||
ApplicationUser userModel = await userManager.GetUserAsync(User);
|
||||
logger.LogInformation(JsonSerializer.Serialize(applicationPreferences));
|
||||
if (userModel.ApplicationPreferences.Id != applicationPreferences.Id || userModel.Id != applicationPreferences.ApplicationUserId) {
|
||||
return BadRequest();
|
||||
}
|
||||
dbContext.Entry(userModel.ApplicationPreferences).CurrentValues.SetValues(applicationPreferences);
|
||||
await userManager.UpdateAsync(userModel);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
42
Props/server/Controllers/WeatherForecastController.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Props.Controllers
|
||||
{
|
||||
// TODO: Create new shop search controller.
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class WeatherForecastController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Summaries = new[]
|
||||
{
|
||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||
};
|
||||
|
||||
private readonly ILogger<WeatherForecastController> _logger;
|
||||
|
||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IEnumerable<WeatherForecast> Get()
|
||||
{
|
||||
var rng = new Random();
|
||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = DateTime.Now.AddDays(index),
|
||||
TemperatureC = rng.Next(-20, 55),
|
||||
Summary = Summaries[rng.Next(Summaries.Length)]
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
51
Props/server/Data/ApplicationDbContext.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Props.Models;
|
||||
using IdentityServer4.EntityFramework.Options;
|
||||
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
|
||||
namespace Props.Data
|
||||
{
|
||||
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
|
||||
{
|
||||
public ApplicationDbContext(
|
||||
DbContextOptions options,
|
||||
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder) {
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<ResultsPreferences>()
|
||||
.Property(e => e.Order)
|
||||
.HasConversion(
|
||||
v => JsonSerializer.Serialize(v, null),
|
||||
v => JsonSerializer.Deserialize<List<ResultsPreferences.Category>>(v, null),
|
||||
new ValueComparer<IList<ResultsPreferences.Category>>(
|
||||
(a, b) => a.SequenceEqual(b),
|
||||
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
|
||||
c => (IList<ResultsPreferences.Category>) c.ToList()
|
||||
)
|
||||
);
|
||||
|
||||
modelBuilder.Entity<SearchOutline>()
|
||||
.Property(e => e.ShopStates)
|
||||
.HasConversion(
|
||||
v => JsonSerializer.Serialize(v, null),
|
||||
v => JsonSerializer.Deserialize<SearchOutline.ShopToggler>(v, null),
|
||||
new ValueComparer<SearchOutline.ShopToggler>(
|
||||
(a, b) => a.Equals(b),
|
||||
c => c.GetHashCode(),
|
||||
c => c.Clone()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
516
Props/server/Data/Migrations/20210712080053_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,516 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Props.Data;
|
||||
|
||||
namespace Props.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20210712080053_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.5");
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||
{
|
||||
b.Property<string>("UserCode")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserCode");
|
||||
|
||||
b.HasIndex("DeviceCode")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.ToTable("DeviceCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ConsumedTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.HasIndex("SubjectId", "ClientId", "Type");
|
||||
|
||||
b.HasIndex("SubjectId", "SessionId", "Type");
|
||||
|
||||
b.ToTable("PersistedGrants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ResultsPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Currency")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableMaxShippingFee")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableUpperPrice")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnknownPurchaseCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnknownRatingCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnknownShipping")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnrated")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LowerPrice")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxResults")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxShippingFee")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MinPurchases")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("MinRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("MinReviews")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ShopStates")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UpperPrice")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SearchOutline");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.ApplicationPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("CacheCommonSearches")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableSearchHistory")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApplicationPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("ResultsPreferences")
|
||||
.HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("SearchOutline")
|
||||
.HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.ApplicationPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("ApplicationPreferences")
|
||||
.HasForeignKey("Props.Shared.Models.ApplicationPreferences", "ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("ApplicationPreferences")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ResultsPreferences")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SearchOutline")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
391
Props/server/Data/Migrations/20210712080053_InitialCreate.cs
Normal file
@@ -0,0 +1,391 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Props.Data.Migrations
|
||||
{
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||
UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
|
||||
SecurityStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
|
||||
PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||
LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DeviceCodes",
|
||||
columns: table => new
|
||||
{
|
||||
UserCode = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
DeviceCode = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
SubjectId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
SessionId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
ClientId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Expiration = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Data = table.Column<string>(type: "TEXT", maxLength: 50000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PersistedGrants",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
Type = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
SubjectId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
SessionId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
ClientId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Expiration = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
ConsumedTime = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
Data = table.Column<string>(type: "TEXT", maxLength: 50000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoleClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
RoleId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApplicationPreferences",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DarkMode = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
CacheCommonSearches = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
EnableSearchHistory = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApplicationPreferences", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApplicationPreferences_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserLogins",
|
||||
columns: table => new
|
||||
{
|
||||
LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
|
||||
ProviderKey = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
RoleId = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserTokens",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
|
||||
Value = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ResultsPreferences",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Order = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ResultsPreferences", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ResultsPreferences_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SearchOutline",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Currency = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
MaxResults = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
MinRating = table.Column<float>(type: "REAL", nullable: false),
|
||||
KeepUnrated = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
EnableUpperPrice = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
UpperPrice = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
LowerPrice = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
MinPurchases = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
KeepUnknownPurchaseCount = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
MinReviews = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
KeepUnknownRatingCount = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
EnableMaxShippingFee = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
MaxShippingFee = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
KeepUnknownShipping = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ShopStates = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SearchOutline", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SearchOutline_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApplicationPreferences_ApplicationUserId",
|
||||
table: "ApplicationPreferences",
|
||||
column: "ApplicationUserId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetRoleClaims_RoleId",
|
||||
table: "AspNetRoleClaims",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "RoleNameIndex",
|
||||
table: "AspNetRoles",
|
||||
column: "NormalizedName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserClaims_UserId",
|
||||
table: "AspNetUserClaims",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserLogins_UserId",
|
||||
table: "AspNetUserLogins",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserRoles_RoleId",
|
||||
table: "AspNetUserRoles",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "EmailIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedEmail");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UserNameIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedUserName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeviceCodes_DeviceCode",
|
||||
table: "DeviceCodes",
|
||||
column: "DeviceCode",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeviceCodes_Expiration",
|
||||
table: "DeviceCodes",
|
||||
column: "Expiration");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PersistedGrants_Expiration",
|
||||
table: "PersistedGrants",
|
||||
column: "Expiration");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
|
||||
table: "PersistedGrants",
|
||||
columns: new[] { "SubjectId", "ClientId", "Type" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PersistedGrants_SubjectId_SessionId_Type",
|
||||
table: "PersistedGrants",
|
||||
columns: new[] { "SubjectId", "SessionId", "Type" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ResultsPreferences_ApplicationUserId",
|
||||
table: "ResultsPreferences",
|
||||
column: "ApplicationUserId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SearchOutline_ApplicationUserId",
|
||||
table: "SearchOutline",
|
||||
column: "ApplicationUserId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApplicationPreferences");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoleClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserLogins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DeviceCodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PersistedGrants");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ResultsPreferences");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SearchOutline");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,514 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Props.Data;
|
||||
|
||||
namespace Props.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.5");
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||
{
|
||||
b.Property<string>("UserCode")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserCode");
|
||||
|
||||
b.HasIndex("DeviceCode")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.ToTable("DeviceCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ConsumedTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.HasIndex("SubjectId", "ClientId", "Type");
|
||||
|
||||
b.HasIndex("SubjectId", "SessionId", "Type");
|
||||
|
||||
b.ToTable("PersistedGrants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ResultsPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Currency")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableMaxShippingFee")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableUpperPrice")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnknownPurchaseCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnknownRatingCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnknownShipping")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("KeepUnrated")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LowerPrice")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxResults")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxShippingFee")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MinPurchases")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("MinRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("MinReviews")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ShopStates")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UpperPrice")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SearchOutline");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.ApplicationPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("CacheCommonSearches")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableSearchHistory")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApplicationPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("ResultsPreferences")
|
||||
.HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.SearchOutline", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("SearchOutline")
|
||||
.HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Shared.Models.ApplicationPreferences", b =>
|
||||
{
|
||||
b.HasOne("Props.Models.ApplicationUser", null)
|
||||
.WithOne("ApplicationPreferences")
|
||||
.HasForeignKey("Props.Shared.Models.ApplicationPreferences", "ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("ApplicationPreferences")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ResultsPreferences")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SearchOutline")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
15
Props/server/Models/ApplicationPreferences.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Props.Shared.Models
|
||||
{
|
||||
public class ApplicationPreferences
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
public bool DarkMode { get; set; }
|
||||
|
||||
public bool CacheCommonSearches { get; set; } = true;
|
||||
|
||||
public bool EnableSearchHistory { get; set; } = true;
|
||||
}
|
||||
}
|
22
Props/server/Models/ApplicationUser.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Props.Shared.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Props.Models
|
||||
{
|
||||
public class ApplicationUser : IdentityUser
|
||||
{
|
||||
[Required]
|
||||
public virtual SearchOutline SearchOutline { get; private set; } = new SearchOutline();
|
||||
|
||||
[Required]
|
||||
public virtual ResultsPreferences ResultsPreferences { get; private set; } = new ResultsPreferences();
|
||||
|
||||
[Required]
|
||||
public virtual ApplicationPreferences ApplicationPreferences {get; private set; } = new ApplicationPreferences();
|
||||
}
|
||||
}
|
34
Props/server/Models/ResultsPreferences.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Props.Models
|
||||
{
|
||||
public class ResultsPreferences
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public IList<Category> Order { get; set; }
|
||||
|
||||
public ResultsPreferences()
|
||||
{
|
||||
Order = new List<Category>(Enum.GetValues<Category>().Length);
|
||||
foreach (Category category in Enum.GetValues<Category>())
|
||||
{
|
||||
Order.Add(category);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Category
|
||||
{
|
||||
RatingPriceRatio,
|
||||
Reviews,
|
||||
Purchases,
|
||||
Price,
|
||||
}
|
||||
}
|
||||
}
|
125
Props/server/Models/SearchOutline.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Models
|
||||
{
|
||||
public class SearchOutline
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
||||
public Currency Currency { get; set; } = Currency.CAD;
|
||||
public int MaxResults { get; set; } = 100;
|
||||
public float MinRating { get; set; } = 0.8f;
|
||||
public bool KeepUnrated { get; set; } = true;
|
||||
public bool EnableUpperPrice { get; set; } = false;
|
||||
private int _upperPrice;
|
||||
|
||||
public int UpperPrice
|
||||
{
|
||||
get
|
||||
{
|
||||
return _upperPrice;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (EnableUpperPrice) _upperPrice = value;
|
||||
}
|
||||
}
|
||||
public int LowerPrice { get; set; }
|
||||
public int MinPurchases { get; set; }
|
||||
public bool KeepUnknownPurchaseCount { get; set; } = true;
|
||||
public int MinReviews { get; set; }
|
||||
public bool KeepUnknownRatingCount { get; set; } = true;
|
||||
public bool EnableMaxShippingFee { get; set; }
|
||||
private int _maxShippingFee;
|
||||
|
||||
public int MaxShippingFee
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxShippingFee;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (EnableMaxShippingFee) _maxShippingFee = value;
|
||||
}
|
||||
}
|
||||
public bool KeepUnknownShipping { get; set; } = true;
|
||||
|
||||
[Required]
|
||||
public ShopToggler ShopStates { get; set; } = new ShopToggler();
|
||||
|
||||
public sealed class ShopToggler : HashSet<string>
|
||||
{
|
||||
public int TotalShops { get; set; }
|
||||
public bool this[string name] {
|
||||
get {
|
||||
return !this.Contains(name);
|
||||
}
|
||||
set {
|
||||
if (value == false && TotalShops - Count <= 1) return;
|
||||
if (value)
|
||||
{
|
||||
this.Remove(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShopToggler Clone() {
|
||||
ShopToggler clone = new ShopToggler();
|
||||
clone.Union(this);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public bool IsShopToggleable(string shop)
|
||||
{
|
||||
return (!Contains(shop) && TotalShops - Count > 1) || Contains(shop);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SearchOutline other = (SearchOutline) obj;
|
||||
return
|
||||
Id == other.Id &&
|
||||
Currency == other.Currency &&
|
||||
MaxResults == other.MaxResults &&
|
||||
MinRating == other.MinRating &&
|
||||
KeepUnrated == other.KeepUnrated &&
|
||||
EnableUpperPrice == other.EnableUpperPrice &&
|
||||
UpperPrice == other.UpperPrice &&
|
||||
LowerPrice == other.LowerPrice &&
|
||||
MinPurchases == other.MinPurchases &&
|
||||
KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount &&
|
||||
MinReviews == other.MinReviews &&
|
||||
KeepUnknownRatingCount == other.KeepUnknownRatingCount &&
|
||||
EnableMaxShippingFee == other.EnableMaxShippingFee &&
|
||||
MaxShippingFee == other.MaxShippingFee &&
|
||||
KeepUnknownShipping == other.KeepUnknownShipping &&
|
||||
ShopStates.Equals(other.ShopStates);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
public SearchOutline DeepCopy() {
|
||||
SearchOutline profile = (SearchOutline)MemberwiseClone();
|
||||
profile.ShopStates = ShopStates.Clone();
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
}
|
26
Props/server/Pages/Error.cshtml
Normal file
@@ -0,0 +1,26 @@
|
||||
@page
|
||||
@model ErrorModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
31
Props/server/Pages/Error.cshtml.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Props.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
private readonly ILogger<ErrorModel> _logger;
|
||||
|
||||
public ErrorModel(ILogger<ErrorModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
36
Props/server/Pages/Shared/_LoginPartial.cshtml
Normal file
@@ -0,0 +1,36 @@
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
@using Props.Models;
|
||||
@inject SignInManager<ApplicationUser> SignInManager
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
|
||||
@{
|
||||
string returnUrl = null;
|
||||
var query = ViewContext.HttpContext.Request.Query;
|
||||
if (query.ContainsKey("returnUrl"))
|
||||
{
|
||||
returnUrl = query["returnUrl"];
|
||||
}
|
||||
}
|
||||
|
||||
<ul class="navbar-nav">
|
||||
@if (SignInManager.IsSignedIn(User))
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/">
|
||||
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Register</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Login</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
3
Props/server/Pages/_ViewImports.cshtml
Normal file
@@ -0,0 +1,3 @@
|
||||
@using Props
|
||||
@namespace Props.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
27
Props/server/Program.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
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 Props
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
// TODO: Rename project to Props
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
27
Props/server/Properties/launchSettings.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:61909",
|
||||
"sslPort": 44392
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Props": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5000;https://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
67
Props/server/Props.csproj
Normal file
@@ -0,0 +1,67 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
|
||||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<SpaRoot>../client/</SpaRoot>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
|
||||
|
||||
<!-- Set this to true if you enable server-side prerendering -->
|
||||
<BuildServerSideRenderer>false</BuildServerSideRenderer>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||
<Content Remove="$(SpaRoot)**" />
|
||||
<None Remove="$(SpaRoot)**" />
|
||||
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Props-Modules\Props.Shop\Framework\Props.Shop.Framework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
||||
<!-- Ensure Node.js is installed -->
|
||||
<Exec Command="node --version" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
|
||||
</Exec>
|
||||
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
|
||||
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
|
||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
|
||||
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
99
Props/server/Startup.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Props.Data;
|
||||
using Props.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Props
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration, IHostEnvironment environment)
|
||||
{
|
||||
Configuration = configuration;
|
||||
Environment = environment;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
public IHostEnvironment Environment { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlite(
|
||||
Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
|
||||
services.AddIdentityServer()
|
||||
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(
|
||||
options => {
|
||||
options.Clients.AddIdentityServerSPA("Props", spa => {
|
||||
spa.WithRedirectUri("/authentication/silent-login-callback.html");
|
||||
spa.WithRedirectUri("/authentication/login-callback");
|
||||
spa.WithLogoutRedirectUri("/authentication/logout-callback");
|
||||
});
|
||||
});
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddIdentityServerJwt();
|
||||
services.AddControllersWithViews();
|
||||
services.AddRazorPages();
|
||||
services.AddSpaStaticFiles(configuration =>
|
||||
{
|
||||
configuration.RootPath = "client/dist";
|
||||
});
|
||||
}
|
||||
|
||||
// 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.UseMigrationsEndPoint();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
if (!env.IsDevelopment()) app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
if (!env.IsDevelopment()) app.UseSpaStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseIdentityServer();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller}/{action=Index}/{id?}");
|
||||
endpoints.MapRazorPages();
|
||||
});
|
||||
|
||||
app.UseSpa(spa =>
|
||||
{
|
||||
spa.Options.SourcePath = "../client"; // "May not exist in published applications" - https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.spaservices.spaoptions.sourcepath?view=aspnetcore-5.0#Microsoft_AspNetCore_SpaServices_SpaOptions_SourcePath
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
spa.UseProxyToSpaDevelopmentServer(Configuration["SPA:BaseUri"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
15
Props/server/WeatherForecast.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Props
|
||||
{
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string Summary { get; set; }
|
||||
}
|
||||
}
|
17
Props/server/appsettings.Development.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"SPA": {
|
||||
"BaseUri": "http://localhost:8080"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"IdentityServer": {
|
||||
"Key": {
|
||||
"Type": "Development"
|
||||
}
|
||||
}
|
||||
}
|
16
Props/server/appsettings.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "DataSource=app.db;Cache=Shared"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Identification": {
|
||||
"RegistrationEnabled": true
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
8
Props/server/options/IdentificationOptions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Props.Options
|
||||
{
|
||||
public class IdentificationOptions
|
||||
{
|
||||
public const string Identification = "Identification";
|
||||
public bool RegistrationEnabled { get; set; }
|
||||
}
|
||||
}
|
BIN
Props/server/wwwroot/favicon.ico
Normal file
After Width: | Height: | Size: 5.3 KiB |
14
Props/vetur.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
// **optional** default: `{}`
|
||||
// override vscode settings
|
||||
// Notice: It only affects the settings used by Vetur.
|
||||
settings: {
|
||||
"vetur.useWorkspaceDependencies": true,
|
||||
"vetur.experimental.templateInterpolationService": true
|
||||
},
|
||||
// **optional** default: `[{ root: './' }]`
|
||||
// support monorepos
|
||||
projects: [
|
||||
"./client"
|
||||
]
|
||||
};
|