Began transition to Vue3.
Implemented logging in and logging out. Implemented authenticated http client. Laid some groundwork for SCSS.
11
.vscode/launch.json
vendored
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Launch and Debug Standalone Blazor WebAssembly App",
|
|
||||||
"type": "blazorwasm",
|
|
||||||
"request": "launch",
|
|
||||||
"cwd": "${workspaceFolder}/src/MultiShop/Server"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Framework\MultiShop.Shop.Framework.csproj" />
|
<ProjectReference Include="..\Framework\MultiShop.Shop.Framework.csproj" />
|
||||||
<ProjectReference Include="..\..\..\SimpleLogger\SimpleLogger.csproj" />
|
<ProjectReference Include="..\..\SimpleLogger\SimpleLogger.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Framework\MultiShop.Shop.Framework.csproj" />
|
<ProjectReference Include="..\Framework\MultiShop.Shop.Framework.csproj" />
|
||||||
<ProjectReference Include="..\..\..\SimpleLogger\SimpleLogger.csproj" />
|
<ProjectReference Include="..\..\SimpleLogger\SimpleLogger.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
43
MultiShop/.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/MultiShop.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
MultiShop/.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
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"${workspaceFolder}/src/MultiShop/Server/MultiShop.Server.csproj",
|
"${workspaceFolder}/server/MultiShop.csproj",
|
||||||
"/property:GenerateFullPaths=true",
|
"/property:GenerateFullPaths=true",
|
||||||
"/consoleloggerparameters:NoSummary"
|
"/consoleloggerparameters:NoSummary"
|
||||||
],
|
],
|
||||||
@ -19,24 +19,20 @@
|
|||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"publish",
|
"publish",
|
||||||
"${workspaceFolder}/src/MultiShop/Server/MultiShop.Server.csproj",
|
"${workspaceFolder}/server/MultiShop.csproj",
|
||||||
"/property:GenerateFullPaths=true",
|
"/property:GenerateFullPaths=true",
|
||||||
"/consoleloggerparameters:NoSummary"
|
"/consoleloggerparameters:NoSummary"
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "watch",
|
"label": "watch all",
|
||||||
"command": "dotnet",
|
"command": "py",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"watch",
|
"scripts/watch_all.py"
|
||||||
"run",
|
|
||||||
"${workspaceFolder}/src/MultiShop/Server/MultiShop.Server.csproj",
|
|
||||||
"/property:GenerateFullPaths=true",
|
|
||||||
"/consoleloggerparameters:NoSummary"
|
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": ["$msCompile", "$node-sass", "$jshint"],
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
3
MultiShop/client/.browserslistrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
> 1%
|
||||||
|
last 2 versions
|
||||||
|
not dead
|
6
MultiShop/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
MultiShop/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
MultiShop/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
MultiShop/client/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# multishop
|
||||||
|
|
||||||
|
## 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
MultiShop/client/babel.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
5
MultiShop/client/jsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
18567
MultiShop/client/package-lock.json
generated
Normal file
48
MultiShop/client/package.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "multishop",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
BIN
MultiShop/client/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
MultiShop/client/public/img/icons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
MultiShop/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
MultiShop/client/public/img/icons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
MultiShop/client/public/img/icons/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
MultiShop/client/public/img/icons/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
MultiShop/client/public/img/icons/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
MultiShop/client/public/img/icons/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
MultiShop/client/public/img/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
MultiShop/client/public/img/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 799 B |
BIN
MultiShop/client/public/img/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
MultiShop/client/public/img/icons/msapplication-icon-144x144.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
MultiShop/client/public/img/icons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
3
MultiShop/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
MultiShop/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
MultiShop/client/public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow:
|
70
MultiShop/client/src/App.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content light">
|
||||||
|
<nav class="navbar" id="nav">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<router-link class="navbar-brand" to="/">MultiShop</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">
|
||||||
|
<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 "./assets/scss/main.scss";
|
||||||
|
import ProfileDisplay from "./components/ProfileDisplay.vue";
|
||||||
|
import ProfileLogIn from "./components/ProfileLogIn.vue";
|
||||||
|
import ProfileLogOut from "./components/ProfileLogOut.vue";
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ProfileDisplay,
|
||||||
|
ProfileLogIn,
|
||||||
|
ProfileLogOut
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch("attemptSilentAuthentication");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
BIN
MultiShop/client/src/assets/images/logo.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
10
MultiShop/client/src/assets/scss/_light.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
@use "../../../node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
#app .content.light {
|
||||||
|
// Add light theme specific scss here.
|
||||||
|
nav.navbar {
|
||||||
|
@extend .navbar-expand-lg;
|
||||||
|
@extend .navbar-light;
|
||||||
|
@extend .bg-light;
|
||||||
|
}
|
||||||
|
}
|
12
MultiShop/client/src/assets/scss/main.scss
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
@use "light";
|
||||||
|
@import "../../../node_modules/bootstrap-icons/font/bootstrap-icons.css";
|
||||||
|
|
||||||
|
html, body, #app, #app > .content {
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
50
MultiShop/client/src/components/ProfileDisplay.vue
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<a v-if="visible" href="authentication/profile" 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 WaitCircle from "@/components/WaitCircle.vue";
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WaitCircle
|
||||||
|
},
|
||||||
|
name: "ProfileDisplay",
|
||||||
|
props: {
|
||||||
|
showUnauthenticated: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
username() {
|
||||||
|
return this.$store.getters.username &&
|
||||||
|
!this.$store.state.identity.loading
|
||||||
|
? this.$store.getters.username
|
||||||
|
: null;
|
||||||
|
},
|
||||||
|
visible() {
|
||||||
|
return !this.showUnauthenticated
|
||||||
|
? this.$store.getters.isAuthenticated ||
|
||||||
|
this.$store.state.identity.loading
|
||||||
|
: true;
|
||||||
|
},
|
||||||
|
isProfileLoading() {
|
||||||
|
return this.$store.state.identity.loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
21
MultiShop/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.state.identity.loading;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.$store.dispatch("beginAuthentication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
21
MultiShop/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.state.identity.loading;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.$store.dispatch("beginDeauthentication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
39
MultiShop/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">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
</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
MultiShop/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>
|
13
MultiShop/client/src/main.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import "./registerServiceWorker";
|
||||||
|
import router from "./router";
|
||||||
|
import store from "./store";
|
||||||
|
import "./assets/scss/main.scss";
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(store);
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
app.mount("#app");
|
32
MultiShop/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
MultiShop/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
MultiShop/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
MultiShop/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 };
|
13
MultiShop/client/src/services/authentication.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { UserManager, WebStorageStateStore } from "oidc-client";
|
||||||
|
|
||||||
|
const userManager = new UserManager({
|
||||||
|
authority: window.location.origin,
|
||||||
|
client_id: "MultiShop",
|
||||||
|
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 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export { userManager };
|
23
MultiShop/client/src/services/http.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
let currentAuthorizationInterceptorID = null;
|
||||||
|
|
||||||
|
const http = axios.create({
|
||||||
|
baseURL: window.location.origin + "/api",
|
||||||
|
timeout: 2000,
|
||||||
|
});
|
||||||
|
|
||||||
|
function addBearerTokenInterceptor(token) {
|
||||||
|
currentAuthorizationInterceptorID = http.interceptors.request.use((config) => {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
return config;
|
||||||
|
}, (err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeBearerTokenInterceptor() {
|
||||||
|
http.interceptors.request.eject(currentAuthorizationInterceptorID);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { http, addBearerTokenInterceptor, removeBearerTokenInterceptor };
|
31
MultiShop/client/src/services/persistence.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const prefix = "MultiShop";
|
||||||
|
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 };
|
99
MultiShop/client/src/store/identity.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import router from "../router";
|
||||||
|
import { userManager } from "../services/authentication";
|
||||||
|
import { addBearerTokenInterceptor, removeBearerTokenInterceptor } from "../services/http";
|
||||||
|
import { get, put } from "../services/persistence";
|
||||||
|
|
||||||
|
const identity = {
|
||||||
|
state: () => ({
|
||||||
|
user: null,
|
||||||
|
loading: true,
|
||||||
|
callbackPath: null,
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
isAuthenticated(state) {
|
||||||
|
return state.user ? !state.user.expired : false;
|
||||||
|
},
|
||||||
|
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 = true;
|
||||||
|
},
|
||||||
|
endAuthenticating(state) {
|
||||||
|
state.loading = false;
|
||||||
|
},
|
||||||
|
authCallbackLocation(state) {
|
||||||
|
state.callbackPath = window.location.pathname;
|
||||||
|
put("callbackPath", state.callbackPath);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
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"
|
||||||
|
});
|
||||||
|
|
||||||
|
context.commit("login", { user });
|
||||||
|
} catch { }
|
||||||
|
context.commit("endAuthenticating");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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");
|
||||||
|
await userManager.removeUser();
|
||||||
|
userManager.signoutRedirect();
|
||||||
|
},
|
||||||
|
async completeAuthentication(context, { user }) {
|
||||||
|
if (!user) return;
|
||||||
|
context.commit("login", { user });
|
||||||
|
router.push(context.getters.authCallbackLocation);
|
||||||
|
context.commit("endAuthenticating");
|
||||||
|
},
|
||||||
|
async completeDeauthentication(context) {
|
||||||
|
if (!context.getters.isAuthenticated) return;
|
||||||
|
context.commit("logout");
|
||||||
|
router.push(context.getters.authCallbackLocation);
|
||||||
|
context.commit("endAuthenticating");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { identity };
|
16
MultiShop/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
MultiShop/client/src/views/About.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
68
MultiShop/client/src/views/Authentication.vue
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<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() {
|
||||||
|
if (this.action === "login-callback") {
|
||||||
|
const user = await userManager.signinRedirectCallback();
|
||||||
|
this.$store.dispatch("completeAuthentication", { user });
|
||||||
|
} else if (this.action === "silent-login-callback") {
|
||||||
|
await userManager.signinSilentCallback();
|
||||||
|
} else if (this.action === "logout-callback") {
|
||||||
|
await userManager.signoutRedirectCallback();
|
||||||
|
this.$store.dispatch("completeDeauthentication");
|
||||||
|
} 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>
|
13
MultiShop/client/src/views/Home.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<img alt="Vue logo" src="../assets/images/logo.png">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Home",
|
||||||
|
components: {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
10
MultiShop/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
MultiShop/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
MultiShop/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
MultiShop/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)
|
||||||
|
})
|
||||||
|
})
|
24
MultiShop/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
MultiShop/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
MultiShop/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) {
|
||||||
|
}
|
||||||
|
}
|
3
MultiShop/package-lock.json
generated
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
28
MultiShop/scripts/reset_db.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
SERVER_DIR = "src/MultiShop/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)
|
29
MultiShop/scripts/watch_all.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
|
||||||
|
SERVER_CSPROJ_DIR = "server"
|
||||||
|
CLIENT_PACKAGE_DIR = "client"
|
||||||
|
|
||||||
|
|
||||||
|
async def exec(cmd, path):
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
os.chdir(os.pardir)
|
||||||
|
os.chdir(path)
|
||||||
|
print("Executing \"{0}\" in \"{1}\".".format(cmd, path))
|
||||||
|
proc = await asyncio.create_subprocess_shell(
|
||||||
|
cmd,
|
||||||
|
stdout=sys.stdout,
|
||||||
|
stderr=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
await proc.wait()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
print("Beginning development servers.")
|
||||||
|
await asyncio.gather(
|
||||||
|
exec("dotnet watch run", SERVER_CSPROJ_DIR),
|
||||||
|
exec("npm run serve", CLIENT_PACKAGE_DIR))
|
||||||
|
|
||||||
|
asyncio.run(main())
|
231
MultiShop/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/
|
@ -2,7 +2,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MultiShop.Server.Controllers
|
namespace MultiShop.Controllers
|
||||||
{
|
{
|
||||||
public class OidcConfigurationController : Controller
|
public class OidcConfigurationController : Controller
|
||||||
{
|
{
|
41
MultiShop/server/Controllers/WeatherForecastController.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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 MultiShop.Controllers
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
[Route("[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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
MultiShop/server/Data/ApplicationDbContext.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using MultiShop.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;
|
||||||
|
|
||||||
|
namespace MultiShop.Data
|
||||||
|
{
|
||||||
|
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
|
||||||
|
{
|
||||||
|
public ApplicationDbContext(
|
||||||
|
DbContextOptions options,
|
||||||
|
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,86 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using MultiShop.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using MultiShop.Server.Data;
|
|
||||||
|
|
||||||
namespace MultiShop.Server.Data.Migrations
|
namespace MultiShop.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20210531175621_InitialCreate")]
|
[Migration("00000000000000_CreateIdentitySchema")]
|
||||||
partial class InitialCreate
|
partial class CreateIdentitySchema
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "5.0.6");
|
.HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2");
|
||||||
|
|
||||||
|
modelBuilder.Entity("MultiShop.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("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||||
{
|
{
|
||||||
@ -253,180 +317,6 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
b.ToTable("AspNetUserTokens");
|
b.ToTable("AspNetUserTokens");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Server.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("MultiShop.Shared.Models.ApplicationProfile", 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("ApplicationProfile");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", 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("ResultsProfile");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", 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("SearchProfile");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
@ -438,7 +328,7 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -447,7 +337,7 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -462,7 +352,7 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -471,45 +361,12 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.ApplicationProfile", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
|
||||||
.WithOne("ApplicationProfile")
|
|
||||||
.HasForeignKey("MultiShop.Shared.Models.ApplicationProfile", "ApplicationUserId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
|
||||||
.WithOne("ResultsProfile")
|
|
||||||
.HasForeignKey("MultiShop.Shared.Models.ResultsProfile", "ApplicationUserId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
|
||||||
.WithOne("SearchProfile")
|
|
||||||
.HasForeignKey("MultiShop.Shared.Models.SearchProfile", "ApplicationUserId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ApplicationProfile")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("ResultsProfile")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("SearchProfile")
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace MultiShop.Server.Data.Migrations
|
namespace MultiShop.Data.Migrations
|
||||||
{
|
{
|
||||||
public partial class InitialCreate : Migration
|
public partial class CreateIdentitySchema : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
@ -106,28 +106,6 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "ApplicationProfile",
|
|
||||||
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_ApplicationProfile", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_ApplicationProfile_AspNetUsers_ApplicationUserId",
|
|
||||||
column: x => x.ApplicationUserId,
|
|
||||||
principalTable: "AspNetUsers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "AspNetUserClaims",
|
name: "AspNetUserClaims",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -213,66 +191,6 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "ResultsProfile",
|
|
||||||
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_ResultsProfile", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_ResultsProfile_AspNetUsers_ApplicationUserId",
|
|
||||||
column: x => x.ApplicationUserId,
|
|
||||||
principalTable: "AspNetUsers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "SearchProfile",
|
|
||||||
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_SearchProfile", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_SearchProfile_AspNetUsers_ApplicationUserId",
|
|
||||||
column: x => x.ApplicationUserId,
|
|
||||||
principalTable: "AspNetUsers",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Restrict);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_ApplicationProfile_ApplicationUserId",
|
|
||||||
table: "ApplicationProfile",
|
|
||||||
column: "ApplicationUserId",
|
|
||||||
unique: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_AspNetRoleClaims_RoleId",
|
name: "IX_AspNetRoleClaims_RoleId",
|
||||||
table: "AspNetRoleClaims",
|
table: "AspNetRoleClaims",
|
||||||
@ -335,25 +253,10 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
name: "IX_PersistedGrants_SubjectId_SessionId_Type",
|
name: "IX_PersistedGrants_SubjectId_SessionId_Type",
|
||||||
table: "PersistedGrants",
|
table: "PersistedGrants",
|
||||||
columns: new[] { "SubjectId", "SessionId", "Type" });
|
columns: new[] { "SubjectId", "SessionId", "Type" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_ResultsProfile_ApplicationUserId",
|
|
||||||
table: "ResultsProfile",
|
|
||||||
column: "ApplicationUserId",
|
|
||||||
unique: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_SearchProfile_ApplicationUserId",
|
|
||||||
table: "SearchProfile",
|
|
||||||
column: "ApplicationUserId",
|
|
||||||
unique: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "ApplicationProfile");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AspNetRoleClaims");
|
name: "AspNetRoleClaims");
|
||||||
|
|
||||||
@ -375,12 +278,6 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "PersistedGrants");
|
name: "PersistedGrants");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "ResultsProfile");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "SearchProfile");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "AspNetRoles");
|
name: "AspNetRoles");
|
||||||
|
|
@ -1,11 +1,11 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
|
using MultiShop.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using MultiShop.Server.Data;
|
|
||||||
|
|
||||||
namespace MultiShop.Server.Data.Migrations
|
namespace MultiShop.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||||
@ -14,7 +14,71 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "5.0.6");
|
.HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2");
|
||||||
|
|
||||||
|
modelBuilder.Entity("MultiShop.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("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||||
{
|
{
|
||||||
@ -251,180 +315,6 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
b.ToTable("AspNetUserTokens");
|
b.ToTable("AspNetUserTokens");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Server.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("MultiShop.Shared.Models.ApplicationProfile", 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("ApplicationProfile");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", 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("ResultsProfile");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", 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("SearchProfile");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
@ -436,7 +326,7 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -445,7 +335,7 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -460,7 +350,7 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -469,45 +359,12 @@ namespace MultiShop.Server.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.ApplicationProfile", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
|
||||||
.WithOne("ApplicationProfile")
|
|
||||||
.HasForeignKey("MultiShop.Shared.Models.ApplicationProfile", "ApplicationUserId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
|
||||||
.WithOne("ResultsProfile")
|
|
||||||
.HasForeignKey("MultiShop.Shared.Models.ResultsProfile", "ApplicationUserId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
|
||||||
.WithOne("SearchProfile")
|
|
||||||
.HasForeignKey("MultiShop.Shared.Models.SearchProfile", "ApplicationUserId");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ApplicationProfile")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("ResultsProfile")
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("SearchProfile")
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
12
MultiShop/server/Models/ApplicationUser.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MultiShop.Models
|
||||||
|
{
|
||||||
|
public class ApplicationUser : IdentityUser
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
64
MultiShop/server/MultiShop.csproj
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
26
MultiShop/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>
|
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -7,16 +7,11 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MultiShop.Server.Pages
|
namespace MultiShop.Pages
|
||||||
{
|
{
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
[IgnoreAntiforgeryToken]
|
|
||||||
public class ErrorModel : PageModel
|
public class ErrorModel : PageModel
|
||||||
{
|
{
|
||||||
public string RequestId { get; set; }
|
|
||||||
|
|
||||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
|
||||||
|
|
||||||
private readonly ILogger<ErrorModel> _logger;
|
private readonly ILogger<ErrorModel> _logger;
|
||||||
|
|
||||||
public ErrorModel(ILogger<ErrorModel> logger)
|
public ErrorModel(ILogger<ErrorModel> logger)
|
||||||
@ -24,6 +19,10 @@ namespace MultiShop.Server.Pages
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string RequestId { get; set; }
|
||||||
|
|
||||||
|
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
public void OnGet()
|
public void OnGet()
|
||||||
{
|
{
|
||||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
36
MultiShop/server/Pages/Shared/_LoginPartial.cshtml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
@using Microsoft.AspNetCore.Identity
|
||||||
|
@using MultiShop.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
MultiShop/server/Pages/_ViewImports.cshtml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@using MultiShop
|
||||||
|
@namespace MultiShop.Pages
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MultiShop.Server
|
namespace MultiShop
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
@ -15,11 +18,6 @@ namespace MultiShop.Server
|
|||||||
|
|
||||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
Host.CreateDefaultBuilder(args)
|
Host.CreateDefaultBuilder(args)
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
|
||||||
config.AddInMemoryCollection(new Dictionary<string, string>() {
|
|
||||||
{"IdentityServer:Clients:MultiShop.Client:Profile", "IdentityServerSPA"}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.ConfigureWebHostDefaults(webBuilder =>
|
.ConfigureWebHostDefaults(webBuilder =>
|
||||||
{
|
{
|
||||||
webBuilder.UseStartup<Startup>();
|
webBuilder.UseStartup<Startup>();
|
@ -1,26 +1,23 @@
|
|||||||
{
|
{
|
||||||
"iisSettings": {
|
"iisSettings": {
|
||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:5738",
|
"applicationUrl": "http://localhost:61909",
|
||||||
"sslPort": 44353
|
"sslPort": 44392
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MultiShop": {
|
"MultiShop": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": "true",
|
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
|
||||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
@ -1,37 +1,36 @@
|
|||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.HttpsPolicy;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.ResponseCompression;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI;
|
||||||
|
using Microsoft.AspNetCore.HttpsPolicy;
|
||||||
|
using Microsoft.AspNetCore.SpaServices.AngularCli;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MultiShop.Data;
|
||||||
|
using MultiShop.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using System.Linq;
|
|
||||||
using MultiShop.Server.Data;
|
|
||||||
using MultiShop.Server.Models;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using System.Security.Claims;
|
|
||||||
|
|
||||||
namespace MultiShop.Server
|
namespace MultiShop
|
||||||
{
|
{
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
public Startup(IConfiguration configuration)
|
public Startup(IConfiguration configuration, IHostEnvironment environment)
|
||||||
{
|
{
|
||||||
Configuration = configuration;
|
Configuration = configuration;
|
||||||
|
Environment = environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfiguration Configuration { get; }
|
public IConfiguration Configuration { get; }
|
||||||
|
public IHostEnvironment Environment { get; }
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddDbContext<ApplicationDbContext>(options => {
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
options.UseLazyLoadingProxies();
|
options.UseSqlite(
|
||||||
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"));
|
Configuration.GetConnectionString("DefaultConnection")));
|
||||||
});
|
|
||||||
|
|
||||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||||
|
|
||||||
@ -39,15 +38,23 @@ namespace MultiShop.Server
|
|||||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||||
|
|
||||||
services.AddIdentityServer()
|
services.AddIdentityServer()
|
||||||
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
|
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(
|
||||||
|
options => {
|
||||||
services.Configure<IdentityOptions>(Options => Options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier); //Note: Despite default, doesn't work without this.
|
options.Clients.AddIdentityServerSPA("MultiShop", spa => {
|
||||||
|
spa.WithRedirectUri("/authentication/silent-login-callback");
|
||||||
|
spa.WithRedirectUri("/authentication/login-callback");
|
||||||
|
spa.WithRedirectUri("/authentication/logout-callback");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
services.AddAuthentication()
|
services.AddAuthentication()
|
||||||
.AddIdentityServerJwt();
|
.AddIdentityServerJwt();
|
||||||
|
|
||||||
services.AddControllersWithViews();
|
services.AddControllersWithViews();
|
||||||
services.AddRazorPages();
|
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.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
@ -57,7 +64,6 @@ namespace MultiShop.Server
|
|||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
app.UseMigrationsEndPoint();
|
app.UseMigrationsEndPoint();
|
||||||
app.UseWebAssemblyDebugging();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -67,20 +73,33 @@ namespace MultiShop.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseBlazorFrameworkFiles();
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
if (!env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSpaStaticFiles();
|
||||||
|
}
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
app.UseIdentityServer();
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
app.UseIdentityServer();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
|
endpoints.MapControllerRoute(
|
||||||
|
name: "default",
|
||||||
|
pattern: "{controller}/{action=Index}/{id?}");
|
||||||
endpoints.MapRazorPages();
|
endpoints.MapRazorPages();
|
||||||
endpoints.MapControllers();
|
});
|
||||||
endpoints.MapFallbackToFile("index.html");
|
|
||||||
|
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
MultiShop/server/WeatherForecast.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MultiShop
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"SPA": {
|
||||||
|
"BaseUri": "http://localhost:8080"
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft": "Warning",
|
"Microsoft": "Warning",
|
||||||
"Microsoft.Hosting.Lifetime": "Information",
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
"MultiShop": "Debug"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"IdentityServer": {
|
"IdentityServer": {
|
13
MultiShop/server/appsettings.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "DataSource=app.db;Cache=Shared"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
BIN
MultiShop/server/wwwroot/favicon.ico
Normal file
After Width: | Height: | Size: 5.3 KiB |
14
MultiShop/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"
|
||||||
|
]
|
||||||
|
};
|
@ -1,39 +0,0 @@
|
|||||||
<CascadingAuthenticationState>
|
|
||||||
<CascadingDependencies Dependencies="@dependencies">
|
|
||||||
<Content>
|
|
||||||
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
|
|
||||||
<Found Context="routeData">
|
|
||||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" Context="authState">
|
|
||||||
<NotAuthorized>
|
|
||||||
@if (!authState.User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
<RedirectToLogin />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<p>You are not authorized to access this resource.</p>
|
|
||||||
}
|
|
||||||
</NotAuthorized>
|
|
||||||
</AuthorizeRouteView>
|
|
||||||
</Found>
|
|
||||||
<NotFound>
|
|
||||||
<LayoutView Layout="@typeof(MainLayout)">
|
|
||||||
<p>Sorry, there's nothing at this address.</p>
|
|
||||||
</LayoutView>
|
|
||||||
</NotFound>
|
|
||||||
</Router>
|
|
||||||
</Content>
|
|
||||||
<LoadingContent Context="Status">
|
|
||||||
<div class="d-flex flex-column align-items-center justify-content-center" style="width: 100vw; height: 100vh;">
|
|
||||||
<div class="my-2">
|
|
||||||
<div class="spinner-border text-primary" role="status">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="my-2">
|
|
||||||
Loading @Status...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</LoadingContent>
|
|
||||||
</CascadingDependencies>
|
|
||||||
</CascadingAuthenticationState>
|
|
@ -1,53 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using MultiShop.Client.Module;
|
|
||||||
using MultiShop.Shared.Models;
|
|
||||||
using MultiShop.Shop.Framework;
|
|
||||||
|
|
||||||
namespace MultiShop.Client
|
|
||||||
{
|
|
||||||
public partial class App
|
|
||||||
{
|
|
||||||
[Inject]
|
|
||||||
private IJSRuntime JS { get; set; }
|
|
||||||
[Inject]
|
|
||||||
private IHttpClientFactory HttpClientFactory {get; set;}
|
|
||||||
private ICollection<RuntimeDependencyManager.Dependency> dependencies = new List<RuntimeDependencyManager.Dependency>();
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
base.OnInitialized();
|
|
||||||
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary<string, IShop>), "Shops", async (publicHttp, authenticatedHttp, auth, logger) => await (new ShopModuleLoader(publicHttp, logger)).GetShops()));
|
|
||||||
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<object> DownloadApplicationProfile(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger)
|
|
||||||
{
|
|
||||||
if (authState.User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
logger.LogDebug($"User is logged in. Attempting to fetch application profile.");
|
|
||||||
HttpResponseMessage response = await http.GetAsync("Profile/Application");
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await response.Content.ReadFromJsonAsync<ApplicationProfile>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplicationProfile profile = await JS.InvokeAsync<ApplicationProfile>("MultiShop.LocalStorageManager.retrieve", "ApplicationProfile");
|
|
||||||
if (profile != null) return profile;
|
|
||||||
return new ApplicationProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
using MultiShop.Shared;
|
|
||||||
using MultiShop.Shared.Models;
|
|
||||||
|
|
||||||
namespace MultiShop.Client.Extensions
|
|
||||||
{
|
|
||||||
public static class ResultProfileExtensions
|
|
||||||
{
|
|
||||||
public static int? CompareListings(this ResultsProfile.Category category, ProductListingInfo a, ProductListingInfo b)
|
|
||||||
{
|
|
||||||
switch (category)
|
|
||||||
{
|
|
||||||
case ResultsProfile.Category.RatingPriceRatio:
|
|
||||||
float? dealDiff = a.RatingToPriceRatio - b.RatingToPriceRatio;
|
|
||||||
if (!dealDiff.HasValue) return null;
|
|
||||||
int dealCeil = (int)Math.Ceiling(Math.Abs(dealDiff.Value));
|
|
||||||
return dealDiff < 0 ? -dealCeil : dealCeil;
|
|
||||||
case ResultsProfile.Category.Price:
|
|
||||||
float priceDiff = b.Listing.UpperPrice - a.Listing.UpperPrice;
|
|
||||||
int priceCeil = (int)Math.Ceiling(Math.Abs(priceDiff));
|
|
||||||
return priceDiff < 0 ? -priceCeil : priceCeil;
|
|
||||||
case ResultsProfile.Category.Purchases:
|
|
||||||
return a.Listing.PurchaseCount - b.Listing.PurchaseCount;
|
|
||||||
case ResultsProfile.Category.Reviews:
|
|
||||||
return a.Listing.ReviewCount - b.Listing.ReviewCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"{category} does not have a defined comparison.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FriendlyName(this ResultsProfile.Category category)
|
|
||||||
{
|
|
||||||
switch (category)
|
|
||||||
{
|
|
||||||
case ResultsProfile.Category.RatingPriceRatio:
|
|
||||||
return "Best rating to price ratio first";
|
|
||||||
case ResultsProfile.Category.Price:
|
|
||||||
return "Lowest price first";
|
|
||||||
case ResultsProfile.Category.Purchases:
|
|
||||||
return "Most purchases first";
|
|
||||||
case ResultsProfile.Category.Reviews:
|
|
||||||
return "Most reviews first";
|
|
||||||
}
|
|
||||||
throw new ArgumentException($"{category} does not have a friendly name defined.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
using MultiShop.Shared.Models;
|
|
||||||
|
|
||||||
namespace MultiShop.Client.Extensions
|
|
||||||
{
|
|
||||||
public static class SearchProfileExtensions
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
using MultiShop.Shared.Models;
|
|
||||||
|
|
||||||
namespace MultiShop.Client.Extensions
|
|
||||||
{
|
|
||||||
public static class applicationProfileExtensions
|
|
||||||
{
|
|
||||||
public static string GetButtonCssClass(this ApplicationProfile applicationProfile, string otherClasses = "", bool outline = false) {
|
|
||||||
if (outline) {
|
|
||||||
return otherClasses + (applicationProfile.DarkMode ? " btn btn-outline-light" : " btn btn-outline-dark");
|
|
||||||
}
|
|
||||||
return otherClasses + (applicationProfile.DarkMode ? " btn btn-light" : " btn btn-dark");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetPageCssClass(this ApplicationProfile applicationProfile, string otherClasses = "") {
|
|
||||||
return otherClasses + (applicationProfile.DarkMode ? " text-white bg-dark" : " text-dark bg-white");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetNavCssClass(this ApplicationProfile applicationProfile, string otherClasses = "") {
|
|
||||||
return otherClasses + (applicationProfile.DarkMode ? " navbar-dark bg-dark" : " navbar-light bg-light");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|