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>
|
||||
<ProjectReference Include="..\Framework\MultiShop.Shop.Framework.csproj" />
|
||||
<ProjectReference Include="..\..\..\SimpleLogger\SimpleLogger.csproj" />
|
||||
<ProjectReference Include="..\..\SimpleLogger\SimpleLogger.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
@ -2,7 +2,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Framework\MultiShop.Shop.Framework.csproj" />
|
||||
<ProjectReference Include="..\..\..\SimpleLogger\SimpleLogger.csproj" />
|
||||
<ProjectReference Include="..\..\SimpleLogger\SimpleLogger.csproj" />
|
||||
</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",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/src/MultiShop/Server/MultiShop.Server.csproj",
|
||||
"${workspaceFolder}/server/MultiShop.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
@ -19,24 +19,20 @@
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/src/MultiShop/Server/MultiShop.Server.csproj",
|
||||
"${workspaceFolder}/server/MultiShop.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"label": "watch all",
|
||||
"command": "py",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"${workspaceFolder}/src/MultiShop/Server/MultiShop.Server.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
"scripts/watch_all.py"
|
||||
],
|
||||
"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.Extensions.Logging;
|
||||
|
||||
namespace MultiShop.Server.Controllers
|
||||
namespace MultiShop.Controllers
|
||||
{
|
||||
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 />
|
||||
using System;
|
||||
using MultiShop.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MultiShop.Server.Data;
|
||||
|
||||
namespace MultiShop.Server.Data.Migrations
|
||||
namespace MultiShop.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20210531175621_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
[Migration("00000000000000_CreateIdentitySchema")]
|
||||
partial class CreateIdentitySchema
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
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 =>
|
||||
{
|
||||
@ -253,180 +317,6 @@ namespace MultiShop.Server.Data.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
@ -438,7 +328,7 @@ namespace MultiShop.Server.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -447,7 +337,7 @@ namespace MultiShop.Server.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -462,7 +352,7 @@ namespace MultiShop.Server.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -471,45 +361,12 @@ namespace MultiShop.Server.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.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
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
using System;
|
||||
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)
|
||||
{
|
||||
@ -106,28 +106,6 @@ namespace MultiShop.Server.Data.Migrations
|
||||
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(
|
||||
name: "AspNetUserClaims",
|
||||
columns: table => new
|
||||
@ -213,66 +191,6 @@ namespace MultiShop.Server.Data.Migrations
|
||||
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(
|
||||
name: "IX_AspNetRoleClaims_RoleId",
|
||||
table: "AspNetRoleClaims",
|
||||
@ -335,25 +253,10 @@ namespace MultiShop.Server.Data.Migrations
|
||||
name: "IX_PersistedGrants_SubjectId_SessionId_Type",
|
||||
table: "PersistedGrants",
|
||||
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)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApplicationProfile");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoleClaims");
|
||||
|
||||
@ -375,12 +278,6 @@ namespace MultiShop.Server.Data.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "PersistedGrants");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ResultsProfile");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SearchProfile");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoles");
|
||||
|
@ -1,11 +1,11 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using MultiShop.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MultiShop.Server.Data;
|
||||
|
||||
namespace MultiShop.Server.Data.Migrations
|
||||
namespace MultiShop.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
@ -14,7 +14,71 @@ namespace MultiShop.Server.Data.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
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 =>
|
||||
{
|
||||
@ -251,180 +315,6 @@ namespace MultiShop.Server.Data.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
@ -436,7 +326,7 @@ namespace MultiShop.Server.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -445,7 +335,7 @@ namespace MultiShop.Server.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -460,7 +350,7 @@ namespace MultiShop.Server.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@ -469,45 +359,12 @@ namespace MultiShop.Server.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("MultiShop.Server.Models.ApplicationUser", null)
|
||||
b.HasOne("MultiShop.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.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
|
||||
}
|
||||
}
|
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.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -7,16 +7,11 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MultiShop.Server.Pages
|
||||
namespace MultiShop.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
private readonly ILogger<ErrorModel> _logger;
|
||||
|
||||
public ErrorModel(ILogger<ErrorModel> logger)
|
||||
@ -24,6 +19,10 @@ namespace MultiShop.Server.Pages
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MultiShop.Server
|
||||
namespace MultiShop
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
@ -15,11 +18,6 @@ namespace MultiShop.Server
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
config.AddInMemoryCollection(new Dictionary<string, string>() {
|
||||
{"IdentityServer:Clients:MultiShop.Client:Profile", "IdentityServerSPA"}
|
||||
})
|
||||
)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
@ -1,26 +1,23 @@
|
||||
{
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:5738",
|
||||
"sslPort": 44353
|
||||
"applicationUrl": "http://localhost:61909",
|
||||
"sslPort": 44392
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"MultiShop": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
@ -1,37 +1,36 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
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 MultiShop.Data;
|
||||
using MultiShop.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
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 Startup(IConfiguration configuration)
|
||||
public Startup(IConfiguration configuration, IHostEnvironment environment)
|
||||
{
|
||||
Configuration = configuration;
|
||||
Environment = environment;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
public IHostEnvironment Environment { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>(options => {
|
||||
options.UseLazyLoadingProxies();
|
||||
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"));
|
||||
});
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlite(
|
||||
Configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
@ -39,15 +38,23 @@ namespace MultiShop.Server
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
|
||||
services.AddIdentityServer()
|
||||
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
|
||||
|
||||
services.Configure<IdentityOptions>(Options => Options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier); //Note: Despite default, doesn't work without this.
|
||||
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(
|
||||
options => {
|
||||
options.Clients.AddIdentityServerSPA("MultiShop", spa => {
|
||||
spa.WithRedirectUri("/authentication/silent-login-callback");
|
||||
spa.WithRedirectUri("/authentication/login-callback");
|
||||
spa.WithRedirectUri("/authentication/logout-callback");
|
||||
});
|
||||
});
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddIdentityServerJwt();
|
||||
|
||||
services.AddControllersWithViews();
|
||||
services.AddRazorPages();
|
||||
services.AddSpaStaticFiles(configuration =>
|
||||
{
|
||||
configuration.RootPath = "client/dist";
|
||||
});
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
@ -57,7 +64,6 @@ namespace MultiShop.Server
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseMigrationsEndPoint();
|
||||
app.UseWebAssemblyDebugging();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -67,20 +73,33 @@ namespace MultiShop.Server
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
if (!env.IsDevelopment())
|
||||
{
|
||||
app.UseSpaStaticFiles();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseIdentityServer();
|
||||
app.UseAuthentication();
|
||||
app.UseIdentityServer();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller}/{action=Index}/{id?}");
|
||||
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": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"MultiShop": "Debug"
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"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");
|
||||
}
|
||||
}
|
||||
}
|