Compare commits
11 Commits
257308e096
...
master
Author | SHA1 | Date | |
---|---|---|---|
948ffc0cbb | |||
8f4c0688f7 | |||
|
bc4bc8eba7 | ||
|
0b19ee33a8 | ||
|
df48e88614 | ||
|
b367854887 | ||
|
cad4268b79 | ||
|
997154efa8 | ||
|
ba413d4330 | ||
|
aff6f8df82 | ||
|
c3bb8983ef |
@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.0.31606.5
|
VisualStudioVersion = 17.0.31606.5
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KTUSA PS", "KTUSA PS\KTUSA PS.csproj", "{D86FA38D-E6E6-48DA-AE1A-007D9FDB3ED8}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KTUSAPS", "KTUSAPS\KTUSAPS.csproj", "{D86FA38D-E6E6-48DA-AE1A-007D9FDB3ED8}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KTUSAPS.Data", "KTUSAPS.Data\KTUSAPS.Data.csproj", "{CF02E79E-4B41-4E48-B2FC-094665980F89}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KTUSAPS.Data", "KTUSAPS.Data\KTUSAPS.Data.csproj", "{CF02E79E-4B41-4E48-B2FC-094665980F89}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
23
KTUSA PS/ClientApp/.gitignore
vendored
23
KTUSA PS/ClientApp/.gitignore
vendored
@@ -1,23 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/dist
|
|
||||||
|
|
||||||
|
|
||||||
# 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?
|
|
@@ -1,24 +0,0 @@
|
|||||||
# clientapp
|
|
||||||
|
|
||||||
## Project setup
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
|
||||||
```
|
|
||||||
npm run serve
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and minifies for production
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lints and fixes files
|
|
||||||
```
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customize configuration
|
|
||||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
|
12269
KTUSA PS/ClientApp/package-lock.json
generated
12269
KTUSA PS/ClientApp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "clientapp",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"serve": "vue-cli-service serve",
|
|
||||||
"build": "vue-cli-service build",
|
|
||||||
"lint": "vue-cli-service lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@popperjs/core": "^2.10.1",
|
|
||||||
"axios": "^0.20.0-0",
|
|
||||||
"bootstrap": "^5.1.1",
|
|
||||||
"cookies-js": "^1.2.3",
|
|
||||||
"core-js": "^3.7.0",
|
|
||||||
"jwt-decode": "^3.1.2",
|
|
||||||
"vue": "^3.0.2",
|
|
||||||
"vue-loader-v16": "npm:vue-loader@^16.0.0-alpha.3",
|
|
||||||
"vue-router": "^4.0.0-rc.5",
|
|
||||||
"vuex": "^4.0.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@vue/cli-plugin-babel": "^4.5.9",
|
|
||||||
"@vue/cli-plugin-eslint": "^4.5.9",
|
|
||||||
"@vue/cli-service": "^4.5.9",
|
|
||||||
"@vue/compiler-sfc": "^3.0.2",
|
|
||||||
"babel-eslint": "^10.1.0",
|
|
||||||
"eslint": "^6.8.0",
|
|
||||||
"eslint-plugin-vue": "^7.1.0",
|
|
||||||
"sass": "^1.33.0",
|
|
||||||
"sass-loader": "^10.2.0"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:vue/vue3-essential",
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"parser": "babel-eslint"
|
|
||||||
},
|
|
||||||
"rules": {}
|
|
||||||
},
|
|
||||||
"browserslist": [
|
|
||||||
"> 1%",
|
|
||||||
"last 2 versions",
|
|
||||||
"not dead"
|
|
||||||
]
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
||||||
<link rel="icon" href="<%= 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>
|
|
@@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<nav-menu></nav-menu>
|
|
||||||
<div v-if="isLocal" class="container">
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<h4 class="alert-heading">Lokali sistemą aptikta</h4>
|
|
||||||
<span>
|
|
||||||
Buvo aptikta, kad aplikacija veikia ant lokalios mašinos. Tai didelis
|
|
||||||
šansas kad sistemoje pateikiama informacija nėra saugi.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="isInsecure" class="container">
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
<h4 class="alert-heading">Nesaugus ryšys</h4>
|
|
||||||
<span>
|
|
||||||
Buvo aptikta, kad yra užmegztas nesaugus ryšys. Tai reiškią kad betkokie
|
|
||||||
duomenys perduodami naudojant sistemą, įskaitant ir prisijungimo tokeną,
|
|
||||||
yra neapsaugoti.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<router-view />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import NavMenu from './components/NavMenu.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'App',
|
|
||||||
components: {
|
|
||||||
NavMenu,
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.$store.dispatch('auth/initialize')
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isLocal() {
|
|
||||||
return (
|
|
||||||
location.hostname === 'localhost' || location.hostname.startsWith('127')
|
|
||||||
)
|
|
||||||
},
|
|
||||||
isInsecure() {
|
|
||||||
return location.protocol !== 'https:'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style></style>
|
|
@@ -1 +0,0 @@
|
|||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,2 +0,0 @@
|
|||||||
@import "custom";
|
|
||||||
@import "~bootstrap/scss/bootstrap";
|
|
@@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<header class="mb-3">
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<router-link :to="{ name: 'Home' }" class="navbar-brand"
|
|
||||||
>KTU SA Problemų sprendimo sistema</router-link
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="navbar-toggler"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="collapse"
|
|
||||||
data-bs-target="#navbarNav"
|
|
||||||
aria-controls="navbarNav"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-label="Toggle navigation"
|
|
||||||
>
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
class="collapse navbar-collapse"
|
|
||||||
:class="{ show: isExpanded }"
|
|
||||||
id="navbarNav"
|
|
||||||
>
|
|
||||||
<ul class="navbar-nav me-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<router-link :to="{ name: 'Home' }" class="nav-link"
|
|
||||||
>Pagrindinis</router-link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="navbar-nav">
|
|
||||||
<span v-if="$store.getters['auth/isValid']" class="navbar-text"
|
|
||||||
>Prisijungta kaip {{ $store.getters['auth/email'] }}</span
|
|
||||||
>
|
|
||||||
<div v-else class="nav-item">
|
|
||||||
<a :href="$store.getters['auth/loginUrl']" class="nav-link"
|
|
||||||
>Prisijungti</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'NavMenu',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isExpanded: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
collapse() {
|
|
||||||
this.isExpanded = false
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle() {
|
|
||||||
this.isExpanded = !this.isExpanded
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -1,107 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<h1>KTU SA Problemų sprendimo sistema</h1>
|
|
||||||
|
|
||||||
<template v-if="$store.getters['auth/isValid']">
|
|
||||||
<div class="alert alert-success">
|
|
||||||
<h4 class="alert-heading">Tu esi prisijungęs</h4>
|
|
||||||
<span>
|
|
||||||
Kliento aplikacija turi tavo saugos raktą. Aplikacija žino, kad tavo
|
|
||||||
el. paštas yra: <b>{{ $store.getters['auth/email'] }}</b>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h2>Visi laukai gaunami iš Azure Active Directory</h2>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Pavadinimas</th>
|
|
||||||
<th scope="col">Reikšmė</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(value, key) in authDataTable" :key="key">
|
|
||||||
<td>{{ key }}</td>
|
|
||||||
<td>
|
|
||||||
<pre>{{ value }}</pre>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h3>Techninė duomenų reprezentacija</h3>
|
|
||||||
<a
|
|
||||||
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens"
|
|
||||||
>Dokumentacija apie laukų reikšmes</a
|
|
||||||
>
|
|
||||||
<pre>{{ $store.state.auth.tokenData }}</pre>
|
|
||||||
<h3>Saugos raktas.</h3>
|
|
||||||
<pre>{{ $store.state.auth.token }}</pre>
|
|
||||||
<h3>Serverio tokeno patikrinimas</h3>
|
|
||||||
<button type="button" class="btn btn-primary" @click="serverVerify">
|
|
||||||
Patikrinti
|
|
||||||
</button>
|
|
||||||
<h5>Verifikacijos atsakas:</h5>
|
|
||||||
<pre>
|
|
||||||
{{ verificationResult }}
|
|
||||||
</pre>
|
|
||||||
</template>
|
|
||||||
<div v-else class="alert alert-danger" role="alert">
|
|
||||||
<h4 class="alert-heading">Tu neprisijungęs</h4>
|
|
||||||
<p>Prašom paspausti prisijungimo mygtuką navigacijos juostoje.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
const names = {
|
|
||||||
aud: 'AppId (Audience)',
|
|
||||||
iss: 'Išdavėjas',
|
|
||||||
iat: 'Išdavimo momentas',
|
|
||||||
nbf: 'Negalioja anksčiau nei',
|
|
||||||
exp: 'Galiojimo pabaiga',
|
|
||||||
email: 'El. paštas',
|
|
||||||
nonce: 'Aplikacijos sugeneruota nepasikartojanti reikšmė',
|
|
||||||
sub: 'Subjektas (Vartotojo Id)',
|
|
||||||
tid: 'Tenanto Identifikatorius',
|
|
||||||
ver: 'OAuth versija',
|
|
||||||
}
|
|
||||||
|
|
||||||
function lookupName(key) {
|
|
||||||
if (names[key]) return names[key]
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
verificationResult: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
authDataTable() {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(this.$store.state.auth.tokenData).map(([key, value]) => [
|
|
||||||
lookupName(key),
|
|
||||||
value,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
serverVerify() {
|
|
||||||
this.verificationResult = null
|
|
||||||
axios
|
|
||||||
.get('/test/authed', {
|
|
||||||
headers: { Authorization: `Bearer ${this.$store.state.auth.token}` },
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
this.verificationResult = response.data
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
alert(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Palaukite kol nustatysime jūsų tapatybe...</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const tokenRegex = /id_token=(.*\..*\..*)&/
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'OIDC',
|
|
||||||
created() {
|
|
||||||
if (this.openIdToken)
|
|
||||||
this.$store.dispatch('auth/setToken', this.openIdToken)
|
|
||||||
this.$router.push({ name: 'Home' })
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
openIdToken() {
|
|
||||||
const matches = this.$route.hash.match(tokenRegex)
|
|
||||||
if (!matches) return null
|
|
||||||
return matches[1]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
@@ -1,23 +0,0 @@
|
|||||||
import { createWebHistory, createRouter } from 'vue-router'
|
|
||||||
import Home from '@/pages/Home.vue'
|
|
||||||
import OidcEndpoint from '@/pages/OidcEndpoint.vue'
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'Home',
|
|
||||||
component: Home,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/oidc',
|
|
||||||
name: 'OpenID connect endpoint',
|
|
||||||
component: OidcEndpoint,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
@@ -1,12 +0,0 @@
|
|||||||
import { createStore, createLogger } from "vuex";
|
|
||||||
import auth from "./modules/auth";
|
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== "production";
|
|
||||||
|
|
||||||
export default createStore({
|
|
||||||
modules: {
|
|
||||||
auth,
|
|
||||||
},
|
|
||||||
strict: debug,
|
|
||||||
plugins: debug ? [createLogger()] : [],
|
|
||||||
});
|
|
@@ -1,145 +0,0 @@
|
|||||||
import Cookies from 'cookies-js'
|
|
||||||
import jwt_decode from 'jwt-decode'
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
const TokenCookieName = 'ktusaktutoken'
|
|
||||||
const ClientIdCookieName = 'ktusakacas'
|
|
||||||
const AuthorityCookieName = 'ktusakeksas'
|
|
||||||
const TenantCookieName = 'ktusalaimis'
|
|
||||||
const NonceCookieName = 'ktusakumpikas'
|
|
||||||
|
|
||||||
const Scope = 'openid email'
|
|
||||||
|
|
||||||
// initial state
|
|
||||||
const state = () => ({
|
|
||||||
token: null,
|
|
||||||
tokenData: null,
|
|
||||||
clientId: null, // 5931fda0-e9e0-4754-80c2-18bcb9d9561a
|
|
||||||
authority: null, // https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0
|
|
||||||
tenant: null, // 3415f2f7-f5a8-4092-b52a-003aaf844853
|
|
||||||
})
|
|
||||||
|
|
||||||
const callbackUrl =
|
|
||||||
window.location.protocol + '//' + window.location.host + '/oidc'
|
|
||||||
|
|
||||||
// getters
|
|
||||||
const getters = {
|
|
||||||
isReady(state) {
|
|
||||||
if (
|
|
||||||
state.clientId == null ||
|
|
||||||
state.authority == null ||
|
|
||||||
state.tenant == null
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
isValid(state, getters) {
|
|
||||||
if (!getters.isReady) return false
|
|
||||||
if (state.token == null || state.tokenData == null) return false
|
|
||||||
const d = state.tokenData
|
|
||||||
if (d.nonce !== state.nonce) return false
|
|
||||||
if (d.iss !== state.authority) return false
|
|
||||||
|
|
||||||
if (d.aud !== state.clientId) return false
|
|
||||||
const now = new Date()
|
|
||||||
const exp = new Date(d.exp * 1000)
|
|
||||||
if (now > exp) return false
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
email(state, getters) {
|
|
||||||
if (!getters.isValid) return null
|
|
||||||
return state.tokenData.email
|
|
||||||
},
|
|
||||||
userId(state, getters) {
|
|
||||||
if (!getters.isValid) return null
|
|
||||||
return state.tokenData.email
|
|
||||||
},
|
|
||||||
loginUrl(state, getters) {
|
|
||||||
if (!getters.isReady) return null
|
|
||||||
return `https://login.microsoftonline.com/${
|
|
||||||
state.tenant
|
|
||||||
}/oauth2/v2.0/authorize?client_id=${
|
|
||||||
state.clientId
|
|
||||||
}&redirect_uri=${encodeURIComponent(
|
|
||||||
callbackUrl
|
|
||||||
)}&response_type=id_token&scope=${Scope}&nonce=${state.nonce}`
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// actions
|
|
||||||
const actions = {
|
|
||||||
async initialize({ commit }) {
|
|
||||||
const token = Cookies.get(TokenCookieName)
|
|
||||||
const primaryClientId = Cookies.get(ClientIdCookieName)
|
|
||||||
const primaryAuthority = Cookies.get(AuthorityCookieName)
|
|
||||||
const primaryTenant = Cookies.get(TenantCookieName)
|
|
||||||
const nonce = Cookies.get(NonceCookieName)
|
|
||||||
if (!nonce) {
|
|
||||||
const newNonce =
|
|
||||||
Date.now().toString(36) +
|
|
||||||
Math.random()
|
|
||||||
.toString(36)
|
|
||||||
.substring(2)
|
|
||||||
Cookies.set(NonceCookieName, newNonce)
|
|
||||||
commit('setNonce', newNonce)
|
|
||||||
} else {
|
|
||||||
commit('setNonce', nonce)
|
|
||||||
}
|
|
||||||
commit('setToken', token)
|
|
||||||
commit('computeTokenVars')
|
|
||||||
commit('setMetadata', [primaryClientId, primaryAuthority, primaryTenant])
|
|
||||||
axios
|
|
||||||
.get('/api/AuthMetadata')
|
|
||||||
.then(response => {
|
|
||||||
Cookies.set(ClientIdCookieName, response.data.clientId)
|
|
||||||
Cookies.set(AuthorityCookieName, response.data.authority)
|
|
||||||
Cookies.set(TenantCookieName, response.data.tenant)
|
|
||||||
commit('setMetadata', [
|
|
||||||
response.data.clientId,
|
|
||||||
response.data.authority,
|
|
||||||
response.data.tenant,
|
|
||||||
])
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async setToken({ commit }, token) {
|
|
||||||
Cookies.set(TokenCookieName, token)
|
|
||||||
commit('setToken', token)
|
|
||||||
commit('computeTokenVars')
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// mutations
|
|
||||||
const mutations = {
|
|
||||||
setToken(state, token) {
|
|
||||||
state.token = token
|
|
||||||
},
|
|
||||||
setNonce(state, nonce) {
|
|
||||||
state.nonce = nonce
|
|
||||||
},
|
|
||||||
computeTokenVars(state) {
|
|
||||||
if (state.token == null) return
|
|
||||||
try {
|
|
||||||
state.tokenData = jwt_decode(state.token)
|
|
||||||
} catch {
|
|
||||||
console.log('Token was invalid.')
|
|
||||||
state.tokenData = null
|
|
||||||
state.token = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setMetadata(state, [clientId, authority, tenant]) {
|
|
||||||
state.clientId = clientId
|
|
||||||
state.authority = authority
|
|
||||||
state.tenant = tenant
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
getters,
|
|
||||||
actions,
|
|
||||||
mutations,
|
|
||||||
}
|
|
@@ -1,8 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
runtimeCompiler: true,
|
|
||||||
chainWebpack: (config) => {
|
|
||||||
config.resolve.alias
|
|
||||||
.set("balm-ui-plus", "balm-ui/dist/balm-ui-plus.js")
|
|
||||||
.set("balm-ui-css", "balm-ui/dist/balm-ui.css");
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,24 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace KTUSA_PS.Controllers
|
|
||||||
{
|
|
||||||
[Route("api/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
public class AuthMetadataController : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
public AuthMetadataController(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public object Index() => new { ClientId = _configuration["ClientId"], Authority = _configuration["Authority"], Tenant = _configuration["Tenant"] };
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace KTUSA_PS.Controllers
|
|
||||||
{
|
|
||||||
[Route("[controller]")]
|
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
|
||||||
public class TestController : ControllerBase
|
|
||||||
{
|
|
||||||
[HttpGet]
|
|
||||||
public object[] Index()
|
|
||||||
{
|
|
||||||
return HttpContext.User.Claims.Select(x => new { Name = x.Type, Value= x.Value }).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("authed")]
|
|
||||||
public bool IsAuthed() => true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,85 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VueCliMiddleware;
|
|
||||||
|
|
||||||
namespace KTUSA_PS
|
|
||||||
{
|
|
||||||
public class Startup
|
|
||||||
{
|
|
||||||
public Startup(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IConfiguration Configuration { get; }
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddControllers();
|
|
||||||
services.AddSpaStaticFiles(configuration =>
|
|
||||||
{
|
|
||||||
configuration.RootPath = "ClientApp";
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
||||||
.AddJwtBearer(options =>
|
|
||||||
{
|
|
||||||
options.Audience = "5931fda0-e9e0-4754-80c2-18bcb9d9561a";
|
|
||||||
options.Authority = "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0";
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddAuthorization((configure) =>
|
|
||||||
{
|
|
||||||
configure.DefaultPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder()
|
|
||||||
.RequireAuthenticatedUser()
|
|
||||||
.Build();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
|
||||||
{
|
|
||||||
if (env.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseRouting();
|
|
||||||
app.UseSpaStaticFiles();
|
|
||||||
app.UseAuthentication();
|
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
|
||||||
{
|
|
||||||
endpoints.MapControllers();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.UseSpa(spa =>
|
|
||||||
{
|
|
||||||
if (env.IsDevelopment())
|
|
||||||
spa.Options.SourcePath = "ClientApp/";
|
|
||||||
else
|
|
||||||
spa.Options.SourcePath = "dist";
|
|
||||||
|
|
||||||
if (env.IsDevelopment())
|
|
||||||
{
|
|
||||||
spa.UseVueCli(npmScript: "serve");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,4 +14,8 @@
|
|||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.1" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="5.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Migrations\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace KTUSAPS.Data.Migrations
|
namespace KTUSAPS.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SAPSDataContext))]
|
[DbContext(typeof(SAPSDataContext))]
|
||||||
[Migration("20210909173149_Initial")]
|
[Migration("20211015122630_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@@ -50,6 +50,9 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
.HasMaxLength(320)
|
.HasMaxLength(320)
|
||||||
.HasColumnType("varchar(320)");
|
.HasColumnType("varchar(320)");
|
||||||
|
|
||||||
|
b.Property<int>("IssueTypeId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("Publishable")
|
b.Property<bool>("Publishable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -62,9 +65,28 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IssueTypeId");
|
||||||
|
|
||||||
b.ToTable("Issues");
|
b.ToTable("Issues");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("KTUSAPS.Data.Model.IssueType", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("NameEn")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("IssueTypes");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -169,6 +191,17 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
b.ToTable("Votes");
|
b.ToTable("Votes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("KTUSAPS.Data.Model.Issue", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("KTUSAPS.Data.Model.IssueType", "IssueType")
|
||||||
|
.WithMany("Issues")
|
||||||
|
.HasForeignKey("IssueTypeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("IssueType");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("KTUSAPS.Data.Model.Issue", "Issue")
|
b.HasOne("KTUSAPS.Data.Model.Issue", "Issue")
|
||||||
@@ -211,6 +244,11 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
b.Navigation("Problem");
|
b.Navigation("Problem");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("KTUSAPS.Data.Model.IssueType", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Issues");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedProblem", b =>
|
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedProblem", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Votes");
|
b.Navigation("Votes");
|
@@ -27,24 +27,19 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Issues",
|
name: "IssueTypes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "int", nullable: false)
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
UserID = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: true)
|
Name = table.Column<string>(type: "longtext", nullable: true)
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
Email = table.Column<string>(type: "varchar(320)", maxLength: 320, nullable: true)
|
NameEn = table.Column<string>(type: "longtext", nullable: true)
|
||||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
|
||||||
Publishable = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
|
||||||
Solved = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
|
||||||
Created = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
|
||||||
Description = table.Column<string>(type: "longtext", nullable: true)
|
|
||||||
.Annotation("MySql:CharSet", "utf8mb4")
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Issues", x => x.Id);
|
table.PrimaryKey("PK_IssueTypes", x => x.Id);
|
||||||
})
|
})
|
||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
@@ -66,6 +61,35 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
})
|
})
|
||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Issues",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
UserID = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Email = table.Column<string>(type: "varchar(320)", maxLength: 320, nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Publishable = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
Solved = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
Created = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Description = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
IssueTypeId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Issues", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Issues_IssueTypes_IssueTypeId",
|
||||||
|
column: x => x.IssueTypeId,
|
||||||
|
principalTable: "IssueTypes",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "PublishedFeedbacks",
|
name: "PublishedFeedbacks",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@@ -147,6 +171,11 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
})
|
})
|
||||||
.Annotation("MySql:CharSet", "utf8mb4");
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Issues_IssueTypeId",
|
||||||
|
table: "Issues",
|
||||||
|
column: "IssueTypeId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_PublishedFeedbacks_IssueId",
|
name: "IX_PublishedFeedbacks_IssueId",
|
||||||
table: "PublishedFeedbacks",
|
table: "PublishedFeedbacks",
|
||||||
@@ -190,6 +219,9 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Solutions");
|
name: "Solutions");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "IssueTypes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -48,6 +48,9 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
.HasMaxLength(320)
|
.HasMaxLength(320)
|
||||||
.HasColumnType("varchar(320)");
|
.HasColumnType("varchar(320)");
|
||||||
|
|
||||||
|
b.Property<int>("IssueTypeId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("Publishable")
|
b.Property<bool>("Publishable")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
@@ -60,9 +63,28 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IssueTypeId");
|
||||||
|
|
||||||
b.ToTable("Issues");
|
b.ToTable("Issues");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("KTUSAPS.Data.Model.IssueType", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("NameEn")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("IssueTypes");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -167,6 +189,17 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
b.ToTable("Votes");
|
b.ToTable("Votes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("KTUSAPS.Data.Model.Issue", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("KTUSAPS.Data.Model.IssueType", "IssueType")
|
||||||
|
.WithMany("Issues")
|
||||||
|
.HasForeignKey("IssueTypeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("IssueType");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedFeedback", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("KTUSAPS.Data.Model.Issue", "Issue")
|
b.HasOne("KTUSAPS.Data.Model.Issue", "Issue")
|
||||||
@@ -209,6 +242,11 @@ namespace KTUSAPS.Data.Migrations
|
|||||||
b.Navigation("Problem");
|
b.Navigation("Problem");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("KTUSAPS.Data.Model.IssueType", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Issues");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedProblem", b =>
|
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedProblem", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Votes");
|
b.Navigation("Votes");
|
||||||
|
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace KTUSAPS.Data.Model
|
namespace KTUSAPS.Data.Model
|
||||||
@@ -22,7 +23,17 @@ namespace KTUSAPS.Data.Model
|
|||||||
[MaxLength]
|
[MaxLength]
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
||||||
public PublishedProblem Problem { get; set; }
|
public int IssueTypeId { get; set; }
|
||||||
public PublishedFeedback Feedback { get; set; }
|
[JsonIgnore]
|
||||||
|
public virtual IssueType IssueType { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual PublishedProblem Problem { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual PublishedFeedback Feedback { get; set; }
|
||||||
|
|
||||||
|
public Issue()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
KTUSAPS.Data/Model/IssueType.cs
Normal file
20
KTUSAPS.Data/Model/IssueType.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Data.Model
|
||||||
|
{
|
||||||
|
public class IssueType
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string NameEn { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual HashSet<Issue> Issues { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -1,18 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ClassDiagram MajorVersion="1" MinorVersion="1">
|
<ClassDiagram MajorVersion="1" MinorVersion="1">
|
||||||
<Class Name="KTUSAPS.Data.Model.Issue">
|
<Class Name="KTUSAPS.Data.Model.Issue">
|
||||||
<Position X="0.5" Y="0.75" Width="1.5" />
|
<Position X="10.25" Y="1" Width="1.5" />
|
||||||
<TypeIdentifier>
|
<TypeIdentifier>
|
||||||
<HashCode>AAACAAJAACAgAAAAAAACAAgIAAAAAAQAAAAAAAAAAAA=</HashCode>
|
<HashCode>AIACAAJQACAgAAAAAAACAAgIAAAAAAQAAAAAAAAAAAA=</HashCode>
|
||||||
<FileName>Model\Issue.cs</FileName>
|
<FileName>Model\Issue.cs</FileName>
|
||||||
</TypeIdentifier>
|
</TypeIdentifier>
|
||||||
<ShowAsAssociation>
|
<ShowAsAssociation>
|
||||||
|
<Property Name="IssueType" />
|
||||||
<Property Name="Problem" />
|
<Property Name="Problem" />
|
||||||
<Property Name="Feedback" />
|
<Property Name="Feedback" />
|
||||||
</ShowAsAssociation>
|
</ShowAsAssociation>
|
||||||
</Class>
|
</Class>
|
||||||
<Class Name="KTUSAPS.Data.Model.PublishedFeedback">
|
<Class Name="KTUSAPS.Data.Model.PublishedFeedback">
|
||||||
<Position X="2.75" Y="3.75" Width="1.5" />
|
<Position X="6.5" Y="3" Width="1.5" />
|
||||||
<TypeIdentifier>
|
<TypeIdentifier>
|
||||||
<HashCode>AAECAAAAAAAAAAAACAIAAAAAAAAAAAQAEAAAAAAAAAA=</HashCode>
|
<HashCode>AAECAAAAAAAAAAAACAIAAAAAAAAAAAQAEAAAAAAAAAA=</HashCode>
|
||||||
<FileName>Model\PublishedFeedback.cs</FileName>
|
<FileName>Model\PublishedFeedback.cs</FileName>
|
||||||
@@ -22,9 +23,9 @@
|
|||||||
</ShowAsAssociation>
|
</ShowAsAssociation>
|
||||||
</Class>
|
</Class>
|
||||||
<Class Name="KTUSAPS.Data.Model.PublishedProblem">
|
<Class Name="KTUSAPS.Data.Model.PublishedProblem">
|
||||||
<Position X="2.75" Y="0.75" Width="1.5" />
|
<Position X="4" Y="0.5" Width="1.5" />
|
||||||
<TypeIdentifier>
|
<TypeIdentifier>
|
||||||
<HashCode>ACECABAAAAEAAAgAAAABAAAAEAAAQAQAEAAAAAAAAAA=</HashCode>
|
<HashCode>ACECABAAAAEAAAgAAAABAAAAAAAAAAQAEAAAAAAAAAA=</HashCode>
|
||||||
<FileName>Model\PublishedProblem.cs</FileName>
|
<FileName>Model\PublishedProblem.cs</FileName>
|
||||||
</TypeIdentifier>
|
</TypeIdentifier>
|
||||||
<ShowAsAssociation>
|
<ShowAsAssociation>
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
</ShowAsCollectionAssociation>
|
</ShowAsCollectionAssociation>
|
||||||
</Class>
|
</Class>
|
||||||
<Class Name="KTUSAPS.Data.Model.Solution">
|
<Class Name="KTUSAPS.Data.Model.Solution">
|
||||||
<Position X="6" Y="0.5" Width="1.5" />
|
<Position X="0.75" Y="0.75" Width="1.5" />
|
||||||
<TypeIdentifier>
|
<TypeIdentifier>
|
||||||
<HashCode>AAACAAIABAAAAQAAAAAAAAAAAAAAAAQAAAAAAAAAAAA=</HashCode>
|
<HashCode>AAACAAIABAAAAQAAAAAAAAAAAAAAAAQAAAAAAAAAAAA=</HashCode>
|
||||||
<FileName>Model\Solution.cs</FileName>
|
<FileName>Model\Solution.cs</FileName>
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
</ShowAsAssociation>
|
</ShowAsAssociation>
|
||||||
</Class>
|
</Class>
|
||||||
<Class Name="KTUSAPS.Data.Model.Vote">
|
<Class Name="KTUSAPS.Data.Model.Vote">
|
||||||
<Position X="6" Y="2.5" Width="1.5" />
|
<Position X="3.5" Y="4.5" Width="1.5" />
|
||||||
<TypeIdentifier>
|
<TypeIdentifier>
|
||||||
<HashCode>AAAAAAIAAAAAEAAAAAAAAAgAAAAAAAAAAAAAAAAAAAA=</HashCode>
|
<HashCode>AAAAAAIAAAAAEAAAAAAAAAgAAAAAAAAAAAAAAAAAAAA=</HashCode>
|
||||||
<FileName>Model\Vote.cs</FileName>
|
<FileName>Model\Vote.cs</FileName>
|
||||||
@@ -55,5 +56,22 @@
|
|||||||
<Property Name="Problem" />
|
<Property Name="Problem" />
|
||||||
</ShowAsAssociation>
|
</ShowAsAssociation>
|
||||||
</Class>
|
</Class>
|
||||||
|
<Class Name="KTUSAPS.Data.Model.IssueType">
|
||||||
|
<Position X="11" Y="5.5" Width="1.5" />
|
||||||
|
<TypeIdentifier>
|
||||||
|
<HashCode>AAECAAAAAAAAAAAAACAAAAQAAAAAAAAAAAAAAAAAAAA=</HashCode>
|
||||||
|
<FileName>Model\IssueType.cs</FileName>
|
||||||
|
</TypeIdentifier>
|
||||||
|
<ShowAsCollectionAssociation>
|
||||||
|
<Property Name="Issues" />
|
||||||
|
</ShowAsCollectionAssociation>
|
||||||
|
</Class>
|
||||||
|
<Class Name="KTUSAPS.Data.Model.Admin">
|
||||||
|
<Position X="0.75" Y="4.5" Width="1.5" />
|
||||||
|
<TypeIdentifier>
|
||||||
|
<HashCode>AAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAA=</HashCode>
|
||||||
|
<FileName>Model\Admin.cs</FileName>
|
||||||
|
</TypeIdentifier>
|
||||||
|
</Class>
|
||||||
<Font Name="Segoe UI" Size="9" />
|
<Font Name="Segoe UI" Size="9" />
|
||||||
</ClassDiagram>
|
</ClassDiagram>
|
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace KTUSAPS.Data.Model
|
namespace KTUSAPS.Data.Model
|
||||||
@@ -22,6 +23,12 @@ namespace KTUSAPS.Data.Model
|
|||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
public int? IssueId { get; set; }
|
public int? IssueId { get; set; }
|
||||||
public Issue Issue { get; set; }
|
[JsonIgnore]
|
||||||
|
public virtual Issue Issue { get; set; }
|
||||||
|
|
||||||
|
public PublishedFeedback()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace KTUSAPS.Data.Model
|
namespace KTUSAPS.Data.Model
|
||||||
@@ -18,20 +19,22 @@ namespace KTUSAPS.Data.Model
|
|||||||
[Required]
|
[Required]
|
||||||
[MaxLength]
|
[MaxLength]
|
||||||
public string ProblemEn { get; set; }
|
public string ProblemEn { get; set; }
|
||||||
[MaxLength]
|
|
||||||
public string ResponseLt { get; set; }
|
|
||||||
[MaxLength]
|
|
||||||
public string ResponseEn { get; set; }
|
|
||||||
|
|
||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
public int? IssueId { get; set; }
|
public int? IssueId { get; set; }
|
||||||
public Issue Issue { get; set; }
|
[JsonIgnore]
|
||||||
|
public virtual Issue Issue { get; set; }
|
||||||
|
|
||||||
public int? SolutionId { get; set; }
|
public int? SolutionId { get; set; }
|
||||||
public Solution Solution { get; set; }
|
[JsonIgnore]
|
||||||
public ICollection<Vote> Votes { get; set; }
|
public virtual Solution Solution { get; set; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual HashSet<Vote> Votes { get; set; }
|
||||||
|
|
||||||
|
public PublishedProblem()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace KTUSAPS.Data.Model
|
namespace KTUSAPS.Data.Model
|
||||||
{
|
{
|
||||||
@@ -13,8 +14,13 @@ namespace KTUSAPS.Data.Model
|
|||||||
[MaxLength]
|
[MaxLength]
|
||||||
public string SolutionEn { get; set; }
|
public string SolutionEn { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public virtual PublishedProblem Problem { get; set; }
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
|
||||||
public PublishedProblem Problem { get; set; }
|
public Solution()
|
||||||
public DateTime Created { get; set; }
|
{
|
||||||
|
Created = DateTime.Now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace KTUSAPS.Data.Model
|
namespace KTUSAPS.Data.Model
|
||||||
@@ -12,7 +13,8 @@ namespace KTUSAPS.Data.Model
|
|||||||
[MaxLength(64)]
|
[MaxLength(64)]
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
public int ProblemId { get; set; }
|
public int ProblemId { get; set; }
|
||||||
public PublishedProblem Problem { get; set; }
|
[JsonIgnore]
|
||||||
|
public virtual PublishedProblem Problem { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ namespace KTUSAPS.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DbSet<IssueType> IssueTypes { get; set; }
|
||||||
public DbSet<Issue> Issues { get; set; }
|
public DbSet<Issue> Issues { get; set; }
|
||||||
public DbSet<PublishedFeedback> PublishedFeedbacks { get; set; }
|
public DbSet<PublishedFeedback> PublishedFeedbacks { get; set; }
|
||||||
public DbSet<PublishedProblem> PublishedProblems { get; set; }
|
public DbSet<PublishedProblem> PublishedProblems { get; set; }
|
||||||
|
8
KTUSAPS/Auth/AdminRequirement.cs
Normal file
8
KTUSAPS/Auth/AdminRequirement.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Auth
|
||||||
|
{
|
||||||
|
public class AdminRequirement : IAuthorizationRequirement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
8
KTUSAPS/Auth/MyIssueRequirement.cs
Normal file
8
KTUSAPS/Auth/MyIssueRequirement.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Auth
|
||||||
|
{
|
||||||
|
public class MyIssueRequirement : IAuthorizationRequirement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
117
KTUSAPS/Auth/SaPsAuthorizationHandler.cs
Normal file
117
KTUSAPS/Auth/SaPsAuthorizationHandler.cs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
using KTUSAPS.Data;
|
||||||
|
using KTUSAPS.Data.Model;
|
||||||
|
using KTUSAPS.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Auth
|
||||||
|
{
|
||||||
|
public class SaPsAuthorizationHandler : IAuthorizationHandler
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
public SaPsAuthorizationHandler(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
this.serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandleAsync(AuthorizationHandlerContext context)
|
||||||
|
{
|
||||||
|
var cache = new AuthProcessingCache(context.User, serviceProvider);
|
||||||
|
foreach (var requirement in context.Requirements)
|
||||||
|
{
|
||||||
|
if (requirement is AdminRequirement adminRequirement)
|
||||||
|
await HandleRequirementAsync(context, adminRequirement, cache);
|
||||||
|
if (requirement is MyIssueRequirement myIssueRequirement)
|
||||||
|
await HandleRequirementAsync(context, myIssueRequirement, cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyIssueRequirement myIssueRequirement, AuthProcessingCache cache)
|
||||||
|
{
|
||||||
|
if(context.Resource is not Issue issue)
|
||||||
|
throw new Exception($"'{nameof(MyIssueRequirement)}' must be issued with resource of type '{nameof(Issue)}'");
|
||||||
|
|
||||||
|
if(issue.UserID == context.User.GetUserId())
|
||||||
|
{
|
||||||
|
context.Succeed(myIssueRequirement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(await cache.DetermineIsAdminAsync())
|
||||||
|
{
|
||||||
|
context.Succeed(myIssueRequirement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement adminRequirement, AuthProcessingCache cache)
|
||||||
|
{
|
||||||
|
if(await cache.DetermineIsAdminAsync())
|
||||||
|
{
|
||||||
|
context.Succeed(adminRequirement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.Fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class AuthProcessingCache
|
||||||
|
{
|
||||||
|
private readonly ClaimsPrincipal _user;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
private bool? isAdmin;
|
||||||
|
public bool IsAdmin => determineIsAdminCached();
|
||||||
|
|
||||||
|
public AuthProcessingCache(ClaimsPrincipal user, IServiceProvider serviceProvider) {
|
||||||
|
_user = user;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool determineIsAdminCached()
|
||||||
|
{
|
||||||
|
if (isAdmin == null)
|
||||||
|
isAdmin = determineIsAdmin();
|
||||||
|
return isAdmin.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool determineIsAdmin()
|
||||||
|
{
|
||||||
|
var objectId = _user.GetObjectId();
|
||||||
|
if (objectId == default)
|
||||||
|
return false;
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var dataContext = scope.ServiceProvider.GetRequiredService<SAPSDataContext>();
|
||||||
|
var admin = dataContext.Admins.Where(a => a.UserId == objectId).FirstOrDefault();
|
||||||
|
if (admin != default)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<bool> determineIsAdminAsync()
|
||||||
|
{
|
||||||
|
var idclaim = _user.Claims.Where(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").FirstOrDefault();
|
||||||
|
if (idclaim == default)
|
||||||
|
return false;
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var dataContext = scope.ServiceProvider.GetRequiredService<SAPSDataContext>();
|
||||||
|
var admin = await dataContext.Admins.Where(a => a.UserId == idclaim.Value).FirstOrDefaultAsync();
|
||||||
|
if (admin != default)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> DetermineIsAdminAsync()
|
||||||
|
{
|
||||||
|
if (isAdmin == null)
|
||||||
|
isAdmin = await determineIsAdminAsync();
|
||||||
|
return isAdmin.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
KTUSAPS/AuthorizeCheckOperationFilter.cs
Normal file
38
KTUSAPS/AuthorizeCheckOperationFilter.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace KTUSAPS
|
||||||
|
{
|
||||||
|
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||||
|
{
|
||||||
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
|
{
|
||||||
|
var hasAuthorize =
|
||||||
|
context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any()
|
||||||
|
|| context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
|
||||||
|
|
||||||
|
if (hasAuthorize)
|
||||||
|
{
|
||||||
|
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||||
|
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
|
||||||
|
|
||||||
|
operation.Security = new List<OpenApiSecurityRequirement>
|
||||||
|
{
|
||||||
|
new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
[
|
||||||
|
new OpenApiSecurityScheme {Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "msad"}
|
||||||
|
}
|
||||||
|
] = new[] {"email", "openid", "profile"}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
KTUSAPS/ClientApp/.gitignore
vendored
Normal file
5
KTUSAPS/ClientApp/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
3
KTUSAPS/ClientApp/.vscode/extensions.json
vendored
Normal file
3
KTUSAPS/ClientApp/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["johnsoncodehk.volar"]
|
||||||
|
}
|
14
KTUSAPS/ClientApp/index.html
Normal file
14
KTUSAPS/ClientApp/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<title>KTU SA Problemų sprendimo sistema</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
483
KTUSAPS/ClientApp/package-lock.json
generated
Normal file
483
KTUSAPS/ClientApp/package-lock.json
generated
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
{
|
||||||
|
"name": "clientapp",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@azure/msal-browser": {
|
||||||
|
"version": "2.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.21.0.tgz",
|
||||||
|
"integrity": "sha512-y+oAUJJ0m7gRUT505iGT4SDXktrRzOE3HZC0+nQyHDI+3O7BKK5NjQUwD/4V8FNNyrhxEtLk99iwJEo61epinQ==",
|
||||||
|
"requires": {
|
||||||
|
"@azure/msal-common": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@azure/msal-common": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Vva3snqmWPHJNDCBb4lz3D0rvZsi/0QikAxHvVFNwtNg5pP4NZE4U34tNiXN+m9KhlQFrZBPkE5rk7dIEOGcWw==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/parser": {
|
||||||
|
"version": "7.16.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
|
||||||
|
"integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A=="
|
||||||
|
},
|
||||||
|
"@popperjs/core": {
|
||||||
|
"version": "2.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
|
||||||
|
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
|
||||||
|
},
|
||||||
|
"@vitejs/plugin-vue": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-AZ78WxvFMYd8JmM/GBV6a6SGGTU0GgN/0/4T+FnMMsLzFEzTeAUwuraapy50ifHZsC+G5SvWs86bvaCPTneFlA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@vue/compiler-core": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/parser": "^7.16.4",
|
||||||
|
"@vue/shared": "3.2.29",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"source-map": "^0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/compiler-dom": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/compiler-core": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/compiler-sfc": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/parser": "^7.16.4",
|
||||||
|
"@vue/compiler-core": "3.2.29",
|
||||||
|
"@vue/compiler-dom": "3.2.29",
|
||||||
|
"@vue/compiler-ssr": "3.2.29",
|
||||||
|
"@vue/reactivity-transform": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"magic-string": "^0.25.7",
|
||||||
|
"postcss": "^8.1.10",
|
||||||
|
"source-map": "^0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/compiler-ssr": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/compiler-dom": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/devtools-api": {
|
||||||
|
"version": "6.0.0-beta.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.21.1.tgz",
|
||||||
|
"integrity": "sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw=="
|
||||||
|
},
|
||||||
|
"@vue/reactivity": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/shared": "3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/reactivity-transform": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/parser": "^7.16.4",
|
||||||
|
"@vue/compiler-core": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"magic-string": "^0.25.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/runtime-core": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/reactivity": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/runtime-dom": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/runtime-core": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29",
|
||||||
|
"csstype": "^2.6.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/server-renderer": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/compiler-ssr": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vue/shared": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw=="
|
||||||
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bootstrap": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q=="
|
||||||
|
},
|
||||||
|
"bootstrap-icons": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-NiR2PqC73AQOPdVSu6GJfnk+hN2z6powcistXk1JgPnKuoV2FSdSl26w931Oz9HYbKCcKUSB6ncZTYJAYJl3QQ=="
|
||||||
|
},
|
||||||
|
"csstype": {
|
||||||
|
"version": "2.6.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.19.tgz",
|
||||||
|
"integrity": "sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ=="
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||||
|
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"esbuild": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esbuild-android-arm64": "0.13.15",
|
||||||
|
"esbuild-darwin-64": "0.13.15",
|
||||||
|
"esbuild-darwin-arm64": "0.13.15",
|
||||||
|
"esbuild-freebsd-64": "0.13.15",
|
||||||
|
"esbuild-freebsd-arm64": "0.13.15",
|
||||||
|
"esbuild-linux-32": "0.13.15",
|
||||||
|
"esbuild-linux-64": "0.13.15",
|
||||||
|
"esbuild-linux-arm": "0.13.15",
|
||||||
|
"esbuild-linux-arm64": "0.13.15",
|
||||||
|
"esbuild-linux-mips64le": "0.13.15",
|
||||||
|
"esbuild-linux-ppc64le": "0.13.15",
|
||||||
|
"esbuild-netbsd-64": "0.13.15",
|
||||||
|
"esbuild-openbsd-64": "0.13.15",
|
||||||
|
"esbuild-sunos-64": "0.13.15",
|
||||||
|
"esbuild-windows-32": "0.13.15",
|
||||||
|
"esbuild-windows-64": "0.13.15",
|
||||||
|
"esbuild-windows-arm64": "0.13.15"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild-windows-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"esbuild-android-arm64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-darwin-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-darwin-arm64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-freebsd-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-freebsd-arm64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-32": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-arm": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-arm64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-mips64le": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-linux-ppc64le": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-netbsd-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-openbsd-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-sunos-64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-windows-32": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild-windows-arm64": {
|
||||||
|
"version": "0.13.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz",
|
||||||
|
"integrity": "sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||||
|
},
|
||||||
|
"follow-redirects": {
|
||||||
|
"version": "1.14.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
|
||||||
|
"integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
|
||||||
|
},
|
||||||
|
"fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"function-bind": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"has": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"function-bind": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is-core-module": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"magic-string": {
|
||||||
|
"version": "0.25.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||||
|
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||||
|
"requires": {
|
||||||
|
"sourcemap-codec": "^1.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"nanoid": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
|
||||||
|
},
|
||||||
|
"path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"picocolors": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||||
|
},
|
||||||
|
"postcss": {
|
||||||
|
"version": "8.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
|
||||||
|
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
|
||||||
|
"requires": {
|
||||||
|
"nanoid": "^3.1.30",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"source-map-js": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "1.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
|
||||||
|
"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-core-module": "^2.8.1",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup": {
|
||||||
|
"version": "2.66.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.1.tgz",
|
||||||
|
"integrity": "sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
|
},
|
||||||
|
"source-map-js": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
||||||
|
},
|
||||||
|
"sourcemap-codec": {
|
||||||
|
"version": "1.4.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||||
|
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
|
||||||
|
},
|
||||||
|
"supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"version": "2.7.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite/-/vite-2.7.13.tgz",
|
||||||
|
"integrity": "sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esbuild": "^0.13.12",
|
||||||
|
"fsevents": "~2.3.2",
|
||||||
|
"postcss": "^8.4.5",
|
||||||
|
"resolve": "^1.20.0",
|
||||||
|
"rollup": "^2.59.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"version": "3.2.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.29.tgz",
|
||||||
|
"integrity": "sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/compiler-dom": "3.2.29",
|
||||||
|
"@vue/compiler-sfc": "3.2.29",
|
||||||
|
"@vue/runtime-dom": "3.2.29",
|
||||||
|
"@vue/server-renderer": "3.2.29",
|
||||||
|
"@vue/shared": "3.2.29"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vue-router": {
|
||||||
|
"version": "4.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.12.tgz",
|
||||||
|
"integrity": "sha512-CPXvfqe+mZLB1kBWssssTiWg4EQERyqJZes7USiqfW9B5N2x+nHlnsM1D3b5CaJ6qgCvMmYJnz+G0iWjNCvXrg==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/devtools-api": "^6.0.0-beta.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vuex": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
|
||||||
|
"requires": {
|
||||||
|
"@vue/devtools-api": "^6.0.0-beta.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
KTUSAPS/ClientApp/package.json
Normal file
26
KTUSAPS/ClientApp/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "clientapp",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vite",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@azure/msal-browser": "^2.21.0",
|
||||||
|
"@popperjs/core": "^2.10.2",
|
||||||
|
"axios": "^0.25.0",
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
|
"bootstrap-icons": "^1.7.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"vue": "^3.2.25",
|
||||||
|
"vue-router": "^4.0.12",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^2.0.0",
|
||||||
|
"sass": "^1.49.0",
|
||||||
|
"vite": "^2.7.2"
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
101
KTUSAPS/ClientApp/src/App.vue
Normal file
101
KTUSAPS/ClientApp/src/App.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<header>
|
||||||
|
<nav-menu />
|
||||||
|
<admin-menu />
|
||||||
|
</header>
|
||||||
|
<data-alert />
|
||||||
|
<confirm-alert ref="ca" />
|
||||||
|
<div class="mb-3"></div>
|
||||||
|
<div v-if="isLocal" class="container">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h4 class="alert-heading">Lokali sistemą aptikta</h4>
|
||||||
|
<span>
|
||||||
|
Buvo aptikta, kad aplikacija veikia ant lokalios mašinos. Tai didelis
|
||||||
|
šansas kad sistemoje pateikiama informacija nėra saugi.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="isInsecure" class="container">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h4 class="alert-heading">Nesaugus ryšys</h4>
|
||||||
|
<span>
|
||||||
|
Buvo aptikta, kad yra užmegztas nesaugus ryšys. Tai reiškią kad betkokie
|
||||||
|
duomenys perduodami naudojant sistemą, įskaitant ir prisijungimo tokeną,
|
||||||
|
yra neapsaugoti.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="slide-fade" mode="out-in">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NavMenu from './components/NavMenu.vue'
|
||||||
|
import DataAlert from './components/DataAlert.vue'
|
||||||
|
import ConfirmAlert from './components/ConfirmAlert.vue'
|
||||||
|
import AdminMenu from './components/AdminMenu.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
NavMenu,
|
||||||
|
DataAlert,
|
||||||
|
ConfirmAlert,
|
||||||
|
AdminMenu,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expiresIn: '',
|
||||||
|
interval: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$store.dispatch('msalAuth/initialize')
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLocal() {
|
||||||
|
return (
|
||||||
|
location.hostname === 'localhost' || location.hostname.startsWith('127')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
isInsecure() {
|
||||||
|
return location.protocol !== 'https:'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async confirm(text, title, options = {}) {
|
||||||
|
const passedOptions = Object.assign(
|
||||||
|
{ text, title, affirmitive: {}, negative: {} },
|
||||||
|
options
|
||||||
|
)
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
passedOptions.affirmitive.action = res
|
||||||
|
passedOptions.negative.action = () =>
|
||||||
|
rej(new Error('User rejected confirmation.'))
|
||||||
|
this.$refs.ca.configure(passedOptions)
|
||||||
|
this.$refs.ca.show()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async deleteConfirmation() {
|
||||||
|
await this.confirm(
|
||||||
|
'Ar tikrai norite ištrinti šį elementą?',
|
||||||
|
'Ištrinimo patvirtinimas',
|
||||||
|
{
|
||||||
|
affirmitive: {
|
||||||
|
text: 'Ištrinti',
|
||||||
|
type: 'danger',
|
||||||
|
},
|
||||||
|
negative: {
|
||||||
|
text: 'Atšaukti',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
5
KTUSAPS/ClientApp/src/assets/_custom.scss
Normal file
5
KTUSAPS/ClientApp/src/assets/_custom.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
$font-family-base: 'Open Sans', sans-serif;
|
||||||
|
|
||||||
|
$primary: #1862a1;
|
||||||
|
$danger: #a1181d;
|
||||||
|
$warning: #eb7d1d;
|
47
KTUSAPS/ClientApp/src/assets/main.scss
Normal file
47
KTUSAPS/ClientApp/src/assets/main.scss
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap');
|
||||||
|
|
||||||
|
@import 'custom';
|
||||||
|
@import 'bootstrap/scss/bootstrap';
|
||||||
|
@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css');
|
||||||
|
|
||||||
|
/* Enter and leave animations can use different */
|
||||||
|
/* durations and timing functions. */
|
||||||
|
.slide-fade-enter-active {
|
||||||
|
transition: all 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all 0.15s cubic-bezier(1, 0.5, 0.8, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-from,
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
transform: translateX(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-to,
|
||||||
|
.slide-fade-leave-from {
|
||||||
|
transform: translateX(0px);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-active {
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-to,
|
||||||
|
.fade-leave-from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
16
KTUSAPS/ClientApp/src/axios.js
Normal file
16
KTUSAPS/ClientApp/src/axios.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import Axios from 'axios'
|
||||||
|
import store from './store'
|
||||||
|
|
||||||
|
export const authAxios = Axios.create({})
|
||||||
|
export const axios = Axios
|
||||||
|
|
||||||
|
authAxios.interceptors.request.use(
|
||||||
|
async (config) => {
|
||||||
|
await store.dispatch('msalAuth/waitTillReady') // Delay requesrt till we ready
|
||||||
|
config.headers['Authorization'] = `Bearer ${store.state.msalAuth.idToken}`
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
() => {}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Axios
|
58
KTUSAPS/ClientApp/src/components/AdminMenu.vue
Normal file
58
KTUSAPS/ClientApp/src/components/AdminMenu.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<nav
|
||||||
|
v-if="$store.state.msalAuth.isAdmin"
|
||||||
|
class="navbar navbar-expand-lg navbar-dark bg-black"
|
||||||
|
>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#adminbarNav"
|
||||||
|
aria-controls="navbarNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="collapse navbar-collapse"
|
||||||
|
:class="{ show: isExpanded }"
|
||||||
|
id="adminbarNav"
|
||||||
|
>
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link :to="{ name: 'Issues' }" class="nav-link"
|
||||||
|
>Pateiktos problemos</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link :to="{ name: 'AdminIssueTypes' }" class="nav-link"
|
||||||
|
>Problemų tipai</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AdminMenu',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExpanded: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
collapse() {
|
||||||
|
this.isExpanded = false
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.isExpanded = !this.isExpanded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
67
KTUSAPS/ClientApp/src/components/Alerter.vue
Normal file
67
KTUSAPS/ClientApp/src/components/Alerter.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="fade">
|
||||||
|
<div
|
||||||
|
v-if="shown"
|
||||||
|
class="alert"
|
||||||
|
:class="{ ['alert-' + type]: true }"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<h4 v-if="title" class="alert-heading">{{ title }}</h4>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import defaults from 'lodash/defaults'
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
title: null,
|
||||||
|
text: 'You have not configured alerter.',
|
||||||
|
type: 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
shown: false,
|
||||||
|
text: 'A simple alert',
|
||||||
|
title: null,
|
||||||
|
type: 'primary',
|
||||||
|
dismisstimer: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show() {
|
||||||
|
this.shown = true
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.shown = false
|
||||||
|
this.cancelAutoDismiss()
|
||||||
|
},
|
||||||
|
cancelAutoDismiss() {
|
||||||
|
if (this.dismisstimer) clearTimeout(this.dismisstimer)
|
||||||
|
this.dismisstimer = null
|
||||||
|
},
|
||||||
|
autoDismiss(timeout) {
|
||||||
|
this.cancelAutoDismiss()
|
||||||
|
this.dismisstimer = setTimeout(() => this.hide(), timeout)
|
||||||
|
},
|
||||||
|
configure(options) {
|
||||||
|
const final = defaults(options, defaultOptions)
|
||||||
|
this.text = final.text
|
||||||
|
this.title = final.title
|
||||||
|
this.type = final.type
|
||||||
|
},
|
||||||
|
alert(options) {
|
||||||
|
this.configure(options)
|
||||||
|
this.cancelAutoDismiss()
|
||||||
|
this.show()
|
||||||
|
},
|
||||||
|
alertTimed(options, timeout = 4000) {
|
||||||
|
this.alert(options)
|
||||||
|
this.autoDismiss(timeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
98
KTUSAPS/ClientApp/src/components/ConfirmAlert.vue
Normal file
98
KTUSAPS/ClientApp/src/components/ConfirmAlert.vue
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="modal" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{{ options.title }}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
{{ options.text }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="'btn-' + options.affirmitive.type"
|
||||||
|
@click="accept"
|
||||||
|
>
|
||||||
|
{{ options.affirmitive.text }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn"
|
||||||
|
:class="'btn-' + options.negative.type"
|
||||||
|
@click="reject"
|
||||||
|
>
|
||||||
|
{{ options.negative.text }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import defaultsDeep from 'lodash/defaultsDeep'
|
||||||
|
import { Modal } from 'bootstrap'
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
title: 'Confirmation',
|
||||||
|
text: 'Do you confirm this action?',
|
||||||
|
affirmitive: {
|
||||||
|
hide: false,
|
||||||
|
text: 'Yes',
|
||||||
|
type: 'primary',
|
||||||
|
action: () => null,
|
||||||
|
},
|
||||||
|
negative: {
|
||||||
|
hide: false,
|
||||||
|
text: 'No',
|
||||||
|
type: 'secondary',
|
||||||
|
action: () => null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modal: null,
|
||||||
|
beenUsed: false,
|
||||||
|
options: Object.assign({}, defaultValues),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
var el = this.$refs.modal
|
||||||
|
el.addEventListener('hide.bs.modal', () => this.handleHideEvent())
|
||||||
|
this.modal = new Modal(el, {})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleHideEvent() {
|
||||||
|
if (!this.beenUsed) {
|
||||||
|
this.options.negative.action()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
this.beenUsed = false
|
||||||
|
this.modal.show()
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.modal.hide()
|
||||||
|
},
|
||||||
|
accept() {
|
||||||
|
this.beenUsed = true
|
||||||
|
this.options.affirmitive.action()
|
||||||
|
this.hide()
|
||||||
|
},
|
||||||
|
reject() {
|
||||||
|
this.beenUsed = true
|
||||||
|
this.options.negative.action()
|
||||||
|
this.hide()
|
||||||
|
},
|
||||||
|
configure(options) {
|
||||||
|
this.options = defaultsDeep({}, options || {}, defaultValues)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
58
KTUSAPS/ClientApp/src/components/DataAlert.vue
Normal file
58
KTUSAPS/ClientApp/src/components/DataAlert.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div ref="modal" class="modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Duomenų naudojimo pranešimas</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Norime jus informuoti, kad sutikdami su šituo duomenų sutikimo jus
|
||||||
|
KTU studentų atstovybei sutinkate perduoti savo duomenis, tokius
|
||||||
|
kaip el. pašto adresas, indentifikacijos kodas, jūsų vardas ir
|
||||||
|
pavardė, ir kitus svarbūs identifikavimo duomenis apie jus.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Norime informuoti, kad ši programa naudoja sausainiukus ir vietinę
|
||||||
|
saugyklą, kad užtikrintų sklandų veikimą.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" @click="confirm">
|
||||||
|
Sutinku
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Modal } from 'bootstrap'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modal: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirm() {
|
||||||
|
this.modal.hide()
|
||||||
|
window.localStorage.setItem('conf', 'true')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
var el = this.$refs.modal
|
||||||
|
this.modal = new Modal(el, {
|
||||||
|
keyboard: false,
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!window.localStorage.getItem('conf')) {
|
||||||
|
this.modal.show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
90
KTUSAPS/ClientApp/src/components/NavMenu.vue
Normal file
90
KTUSAPS/ClientApp/src/components/NavMenu.vue
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<router-link :to="{ name: 'Home' }" class="navbar-brand"
|
||||||
|
>KTU SA Problemų sprendimo sistema</router-link
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="collapse navbar-collapse"
|
||||||
|
:class="{ show: isExpanded }"
|
||||||
|
id="navbarNav"
|
||||||
|
>
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link :to="{ name: 'Home' }" class="nav-link"
|
||||||
|
>Pagrindinis</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<template v-if="$store.state.msalAuth.isLoggedIn">
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link :to="{ name: 'Submit' }" class="nav-link"
|
||||||
|
>Pateikti problemą</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link :to="{ name: 'Problems' }" class="nav-link"
|
||||||
|
>Problemos</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link :to="{ name: 'Feedbacks' }" class="nav-link"
|
||||||
|
>Atsiliepimai</router-link
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<li v-if="$root.isLocal" class="nav-item">
|
||||||
|
<a href="/swagger" class="nav-link">SUI</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-nav">
|
||||||
|
<template v-if="$store.state.msalAuth.isLoggedIn">
|
||||||
|
<span class="navbar-text"
|
||||||
|
>Prisijungta kaip {{ $store.state.msalAuth.displayName }}
|
||||||
|
</span>
|
||||||
|
<div class="nav-item">
|
||||||
|
<a href="#" @click="LogoutMsal" class="nav-link">Atsijungti</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="nav-item">
|
||||||
|
<a href="#" @click="LoginMsal" class="nav-link">Prisijungti</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { LoginMsal, LogoutMsal } from '@/msal'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NavMenu',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExpanded: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
LoginMsal,
|
||||||
|
LogoutMsal,
|
||||||
|
collapse() {
|
||||||
|
this.isExpanded = false
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.isExpanded = !this.isExpanded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
40
KTUSAPS/ClientApp/src/components/forms/FeedbackForm.vue
Normal file
40
KTUSAPS/ClientApp/src/components/forms/FeedbackForm.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="feedbackLtTextArea" class="form-label"
|
||||||
|
>Lietuviškas Aprašymas</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="feedbackLt"
|
||||||
|
class="form-control"
|
||||||
|
id="feedbackLtTextArea"
|
||||||
|
rows="4"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="feedbackEnTextArea" class="form-label"
|
||||||
|
>Angliškas Aprašymas</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="feedbackEn"
|
||||||
|
class="form-control"
|
||||||
|
id="feedbackEnTextArea"
|
||||||
|
rows="4"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapModel } from './formHelpers'
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
computed: {
|
||||||
|
...mapModel(['feedbackLt', 'feedbackEn']),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
27
KTUSAPS/ClientApp/src/components/forms/IssueTypeForm.vue
Normal file
27
KTUSAPS/ClientApp/src/components/forms/IssueTypeForm.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nameInput" class="form-label">Lietuviškas pavadinimas</label>
|
||||||
|
<input v-model="name" type="text" class="form-control" id="nameInput" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nameEnInput" class="form-label">Angliškas pavadinimas</label>
|
||||||
|
<input v-model="nameEn" type="text" class="form-control" id="nameEnInput" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapModel } from './formHelpers'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
computed: {
|
||||||
|
...mapModel(['name', 'nameEn']),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
27
KTUSAPS/ClientApp/src/components/forms/formHelpers.js
Normal file
27
KTUSAPS/ClientApp/src/components/forms/formHelpers.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export function autoComputed(propName, defaultValue) {
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
return this.modelValue[propName] || defaultValue
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit('update:modelValue', {
|
||||||
|
...this.modelValue,
|
||||||
|
[propName]: value,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapModel(props) {
|
||||||
|
const computedValues = {}
|
||||||
|
if (Array.isArray(props)) {
|
||||||
|
props.forEach((propName) => {
|
||||||
|
computedValues[propName] = autoComputed(propName, '')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Object.entries(props).forEach(([propName, defaultValue]) => {
|
||||||
|
computedValues[propName] = autoComputed(propName, defaultValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return computedValues
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import router from './router'
|
|||||||
import store from './store'
|
import store from './store'
|
||||||
import './assets/main.scss'
|
import './assets/main.scss'
|
||||||
import 'bootstrap'
|
import 'bootstrap'
|
||||||
|
import './msal'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
@@ -11,5 +12,3 @@ app.use(router)
|
|||||||
app.use(store)
|
app.use(store)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
window.r = router
|
|
169
KTUSAPS/ClientApp/src/msal.js
Normal file
169
KTUSAPS/ClientApp/src/msal.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import * as msal from '@azure/msal-browser'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const ClientIdCookieName = 'ktusakacas'
|
||||||
|
const AuthorityCookieName = 'ktusakeksas'
|
||||||
|
const TenantCookieName = 'ktusalaimis'
|
||||||
|
|
||||||
|
const RequestedScopes = ['openid', 'email', 'profile']
|
||||||
|
|
||||||
|
const msalState = {
|
||||||
|
msal: null,
|
||||||
|
clientId: null, // 5931fda0-e9e0-4754-80c2-18bcb9d9561a
|
||||||
|
authority: null, // https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0
|
||||||
|
tenant: null, // 3415f2f7-f5a8-4092-b52a-003aaf844853,
|
||||||
|
stateChangeCallbacks: [],
|
||||||
|
|
||||||
|
isLoggedIn: false,
|
||||||
|
accessToken: null,
|
||||||
|
idToken: null,
|
||||||
|
email: null,
|
||||||
|
displayName: null,
|
||||||
|
|
||||||
|
debugFullTokenResponse: null,
|
||||||
|
msalRefreshTimer: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeMSAL() {
|
||||||
|
if (msalState.msal != null) {
|
||||||
|
throw new Error('MSAL was attempted to initialize second time')
|
||||||
|
}
|
||||||
|
await __loadAuthParameters()
|
||||||
|
const msalConfig = {
|
||||||
|
auth: {
|
||||||
|
clientId: msalState.clientId,
|
||||||
|
authority: `https://login.microsoftonline.com/${msalState.tenant}`,
|
||||||
|
redirectUri: window.location.protocol + '//' + window.location.host + '/',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
msalState.msalRefreshTimer = setInterval(__refreshToken, 10 * 60 * 1000)
|
||||||
|
|
||||||
|
msalState.msal = new msal.PublicClientApplication(msalConfig)
|
||||||
|
|
||||||
|
msalState.msal.handleRedirectPromise().then(__handleResponse)
|
||||||
|
|
||||||
|
window.msalState = msalState
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WatchMsalState(callback) {
|
||||||
|
msalState.stateChangeCallbacks.push(callback)
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetMsalState() {
|
||||||
|
return {
|
||||||
|
accessToken: msalState.accessToken,
|
||||||
|
idToken: msalState.idToken,
|
||||||
|
isLoggedIn: msalState.isLoggedIn,
|
||||||
|
debugFullTokenResponse: msalState.debugFullTokenResponse,
|
||||||
|
debugAccountInfo: msalState.debugAccountInfo,
|
||||||
|
email: msalState.email,
|
||||||
|
displayName: msalState.displayName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoginMsal() {
|
||||||
|
msalState.msal.loginRedirect({
|
||||||
|
scopes: RequestedScopes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogoutMsal() {
|
||||||
|
msalState.msal.logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __refreshToken() {
|
||||||
|
if (!msalState.isLoggedIn) return
|
||||||
|
msalState.debugFullTokenResponse = await msalState.msal
|
||||||
|
.acquireTokenSilent({ scopes: RequestedScopes })
|
||||||
|
.catch((error) => {
|
||||||
|
if (error instanceof msal.InteractionRequiredAuthError) {
|
||||||
|
// fallback to interaction when silent call fails
|
||||||
|
return msalState.msal.acquireTokenRedirect({
|
||||||
|
scopes: RequestedScopes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
__responseObjectToMsalState()
|
||||||
|
__stateChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __handleResponse(response) {
|
||||||
|
if (response !== null) {
|
||||||
|
if (__isAccountAceptable(response.account)) {
|
||||||
|
msalState.msal.setActiveAccount(response)
|
||||||
|
msalState.debugFullTokenResponse = response
|
||||||
|
|
||||||
|
__responseObjectToMsalState()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msalState.msal
|
||||||
|
.getAllAccounts()
|
||||||
|
.filter(__isAccountAceptable)
|
||||||
|
.forEach((account) => {
|
||||||
|
msalState.msal.setActiveAccount(account)
|
||||||
|
})
|
||||||
|
|
||||||
|
const account = msalState.msal.getActiveAccount()
|
||||||
|
if (account != null) {
|
||||||
|
msalState.debugFullTokenResponse = await msalState.msal
|
||||||
|
.acquireTokenSilent({ scopes: RequestedScopes })
|
||||||
|
.catch((error) => {
|
||||||
|
if (error instanceof msal.InteractionRequiredAuthError) {
|
||||||
|
// fallback to interaction when silent call fails
|
||||||
|
return msalState.msal.acquireTokenRedirect({
|
||||||
|
scopes: RequestedScopes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
__responseObjectToMsalState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__stateChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
function __responseObjectToMsalState() {
|
||||||
|
msalState.isLoggedIn = true
|
||||||
|
msalState.accessToken = msalState.debugFullTokenResponse.accessToken
|
||||||
|
msalState.idToken = msalState.debugFullTokenResponse.idToken
|
||||||
|
msalState.email = msalState.debugFullTokenResponse.idTokenClaims.email
|
||||||
|
msalState.displayName = msalState.debugFullTokenResponse.idTokenClaims.name
|
||||||
|
}
|
||||||
|
|
||||||
|
function __isAccountAceptable(account) {
|
||||||
|
if (account.tenantId != msalState.tenant) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function __stateChanged() {
|
||||||
|
msalState.stateChangeCallbacks.forEach((cb) => cb())
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __loadAuthParameters() {
|
||||||
|
await __loadAuthParametersLocalStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __loadAuthParametersLocalStorage() {
|
||||||
|
const clientId = localStorage.getItem(ClientIdCookieName)
|
||||||
|
const authority = localStorage.getItem(AuthorityCookieName)
|
||||||
|
const tenant = localStorage.getItem(TenantCookieName)
|
||||||
|
if (clientId == null || authority == null || tenant == null) {
|
||||||
|
await __fetchAuthParameters()
|
||||||
|
localStorage.setItem(ClientIdCookieName, msalState.clientId)
|
||||||
|
localStorage.setItem(AuthorityCookieName, msalState.authority)
|
||||||
|
localStorage.setItem(TenantCookieName, msalState.tenant)
|
||||||
|
} else {
|
||||||
|
msalState.clientId = clientId
|
||||||
|
msalState.authority = authority
|
||||||
|
msalState.tenant = tenant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __fetchAuthParameters() {
|
||||||
|
var response = await axios.get('/api/AuthMetadata')
|
||||||
|
msalState.clientId = response.data.clientId
|
||||||
|
msalState.authority = response.data.authority
|
||||||
|
msalState.tenant = response.data.tenant
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeMSAL()
|
34
KTUSAPS/ClientApp/src/pages/Feedbacks.vue
Normal file
34
KTUSAPS/ClientApp/src/pages/Feedbacks.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="card my-4" v-for="f in feedbacks" :key="f.id">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-text text-muted">Paskelbta: {{ f.created }}</h6>
|
||||||
|
<h5 class="card-title">Atsiliepimas:</h5>
|
||||||
|
<p class="card-text">{{ f.feedbackLt }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
feedbacks: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await axios.get('/api/PublishedFeedbacks')
|
||||||
|
this.feedbacks = response.data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
93
KTUSAPS/ClientApp/src/pages/Home.vue
Normal file
93
KTUSAPS/ClientApp/src/pages/Home.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<h1>KTU SA Problemų sprendimo sistema</h1>
|
||||||
|
|
||||||
|
<template v-if="$store.state.msalAuth.isLoggedIn">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<h4 class="alert-heading">Tu esi prisijungęs</h4>
|
||||||
|
<span>
|
||||||
|
Kliento aplikacija turi tavo saugos raktą. Aplikacija žino, kad tavo
|
||||||
|
el. paštas yra: <b>{{ $store.state.msalAuth.email }}</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<template v-if="$store.state.msalAuth.isAdmin">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<span> Tu esi administratorius. </span>
|
||||||
|
</div>
|
||||||
|
<h3>Prieigos raktas (Access token)</h3>
|
||||||
|
<a
|
||||||
|
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens"
|
||||||
|
>Dokumentacija</a
|
||||||
|
>
|
||||||
|
<pre>{{ $store.state.msalAuth.accessToken }}</pre>
|
||||||
|
|
||||||
|
<h3>Indentifikacijos raktas (ID Token)</h3>
|
||||||
|
<a
|
||||||
|
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens"
|
||||||
|
>Dokumentacija</a
|
||||||
|
>
|
||||||
|
<pre>{{ $store.state.msalAuth.idToken }}</pre>
|
||||||
|
|
||||||
|
<h4>Duomenys gaunami iš Indentifikacijos rakto (ID Token)</h4>
|
||||||
|
<pre>{{
|
||||||
|
$store.state.msalAuth.debugFullTokenResponse.idTokenClaims
|
||||||
|
}}</pre>
|
||||||
|
<h3>Autorizuotos sritys</h3>
|
||||||
|
<pre>{{ $store.state.msalAuth.debugFullTokenResponse.scopes }}</pre>
|
||||||
|
<h3>Serverio tokeno patikrinimas</h3>
|
||||||
|
<button type="button" class="btn btn-primary" @click="serverVerify">
|
||||||
|
Patikrinti
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="serverAdminVerify"
|
||||||
|
>
|
||||||
|
Patikrinti ar admin
|
||||||
|
</button>
|
||||||
|
<h5>Verifikacijos atsakas:</h5>
|
||||||
|
<pre>{{ verificationResult }}</pre>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<div v-else class="alert alert-danger" role="alert">
|
||||||
|
<h4 class="alert-heading">Tu neprisijungęs</h4>
|
||||||
|
<p>Prašom paspausti prisijungimo mygtuką navigacijos juostoje.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { authAxios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
verificationResult: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
serverVerify() {
|
||||||
|
this.verificationResult = null
|
||||||
|
authAxios
|
||||||
|
.get('/api/AuthMetadata/authed')
|
||||||
|
.then((response) => {
|
||||||
|
this.verificationResult = response.data
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
alert(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
serverAdminVerify() {
|
||||||
|
this.verificationResult = null
|
||||||
|
authAxios
|
||||||
|
.get('/api/AuthMetadata/Admin')
|
||||||
|
.then((response) => {
|
||||||
|
this.verificationResult = response.data
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
alert(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
79
KTUSAPS/ClientApp/src/pages/Issues.vue
Normal file
79
KTUSAPS/ClientApp/src/pages/Issues.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-success" v-if="ok">{{ ok }}</div>
|
||||||
|
<div class="alert alert-danger" v-if="error">{{ error }}</div>
|
||||||
|
<div class="card my-4" v-for="i in issues" :key="i.id">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-text text-muted">Pateikta: {{ i.created }}</h6>
|
||||||
|
<h6 class="card-text text-muted">El paštas: {{ i.email }}</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Skelbtinas: {{ i.publishable ? '✔' : '❌' }}
|
||||||
|
</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Išsprestą: {{ i.solved ? '✔' : '❌' }}
|
||||||
|
</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Tipas: {{ issueTypes[i.issueTypeId]?.name }}
|
||||||
|
</h6>
|
||||||
|
<h5 class="card-title">Aprašymas:</h5>
|
||||||
|
<p class="card-text">{{ i.description }}</p>
|
||||||
|
<button class="btn btn-danger mx-1" @click="deleteIssue(i.id)">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
Ištrinti
|
||||||
|
</button>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'IssueNewProblem', params: { id: i.id } }"
|
||||||
|
class="btn btn-primary mx-1"
|
||||||
|
>Paskelbti problemą</router-link
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'IssueNewFeedback', params: { id: i.id } }"
|
||||||
|
class="btn btn-primary mx-1"
|
||||||
|
>Paskelbti atsiliepimą</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios, authAxios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issues: [],
|
||||||
|
issueTypes: {},
|
||||||
|
ok: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await authAxios.get('/api/Issues')
|
||||||
|
this.issues = response.data
|
||||||
|
const response2 = await axios.get('/api/IssueTypes')
|
||||||
|
response2.data.forEach((it) => {
|
||||||
|
this.issueTypes[it.id] = it
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async deleteIssue(id) {
|
||||||
|
this.error = null
|
||||||
|
this.ok = null
|
||||||
|
try {
|
||||||
|
await this.$root.deleteConfirmation()
|
||||||
|
await authAxios.delete(`/api/Issues/${id}`)
|
||||||
|
this.ok = 'Sėkmingai ištrintą'
|
||||||
|
await this.fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
85
KTUSAPS/ClientApp/src/pages/NewFeedback.vue
Normal file
85
KTUSAPS/ClientApp/src/pages/NewFeedback.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-success" v-if="ok">{{ ok }}</div>
|
||||||
|
<div class="alert alert-danger" v-if="error">{{ error }}</div>
|
||||||
|
<h1>Naujas atsiliepimas</h1>
|
||||||
|
<div class="card my-4">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-text text-muted">Pateikta: {{ issue.created }}</h6>
|
||||||
|
<h6 class="card-text text-muted">El paštas: {{ issue.email }}</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Skelbtinas: {{ issue.publishable ? '✔' : '❌' }}
|
||||||
|
</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Išsprestą: {{ issue.solved ? '✔' : '❌' }}
|
||||||
|
</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Tipas: {{ issueTypes[issue.issueTypeId]?.name }}
|
||||||
|
</h6>
|
||||||
|
<h5 class="card-title">Aprašymas:</h5>
|
||||||
|
<p class="card-text">{{ issue.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="card col-lg-6 p-5">
|
||||||
|
<feedback-form v-model="feedback" />
|
||||||
|
<button @click="create" class="btn btn-primary btn-lg">
|
||||||
|
Sukurti naują atsiliepimą
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios, authAxios } from '@/axios'
|
||||||
|
import FeedbackForm from '@/components/forms/FeedbackForm.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FeedbackForm,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issue: null,
|
||||||
|
issueTypes: {},
|
||||||
|
feedback: {
|
||||||
|
feedbackEn: '',
|
||||||
|
feedbackLt: '',
|
||||||
|
issueId: null,
|
||||||
|
},
|
||||||
|
ok: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await authAxios.get(
|
||||||
|
`/api/Issues/${this.$route.params.id}`
|
||||||
|
)
|
||||||
|
this.issue = response.data
|
||||||
|
const response2 = await axios.get('/api/IssueTypes')
|
||||||
|
response2.data.forEach((it) => {
|
||||||
|
this.issueTypes[it.id] = it
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async create() {
|
||||||
|
try {
|
||||||
|
this.feedback.issueId = this.issue.id
|
||||||
|
await authAxios.post('/api/PublishedFeedbacks', this.feedback)
|
||||||
|
this.ok = 'Sukurtą.'
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: 'fetchData',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
102
KTUSAPS/ClientApp/src/pages/NewProblem.vue
Normal file
102
KTUSAPS/ClientApp/src/pages/NewProblem.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-success" v-if="ok">{{ ok }}</div>
|
||||||
|
<div class="alert alert-danger" v-if="error">{{ error }}</div>
|
||||||
|
<h1>Nauja problema</h1>
|
||||||
|
<div class="card my-4">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-text text-muted">Pateikta: {{ issue.created }}</h6>
|
||||||
|
<h6 class="card-text text-muted">El paštas: {{ issue.email }}</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Skelbtinas: {{ issue.publishable ? '✔' : '❌' }}
|
||||||
|
</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Išsprestą: {{ issue.solved ? '✔' : '❌' }}
|
||||||
|
</h6>
|
||||||
|
<h6 class="card-text text-muted">
|
||||||
|
Tipas: {{ issueTypes[issue.issueTypeId]?.name }}
|
||||||
|
</h6>
|
||||||
|
<h5 class="card-title">Aprašymas:</h5>
|
||||||
|
<p class="card-text">{{ issue.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="card col-lg-6 p-5">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="problemLtTextArea" class="form-label"
|
||||||
|
>Lietuviškas Aprašymas</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="problem.problemLt"
|
||||||
|
class="form-control"
|
||||||
|
id="problemLtTextArea"
|
||||||
|
rows="4"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="problemEnTextArea" class="form-label"
|
||||||
|
>Angliškas Aprašymas</label
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
v-model="problem.problemEn"
|
||||||
|
class="form-control"
|
||||||
|
id="problemEnTextArea"
|
||||||
|
rows="4"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<button @click="create" class="btn btn-primary btn-lg">
|
||||||
|
Sukurti naują problemą
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios, authAxios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issue: null,
|
||||||
|
issueTypes: {},
|
||||||
|
problem: {
|
||||||
|
problemLt: '',
|
||||||
|
problemEn: '',
|
||||||
|
issueId: null,
|
||||||
|
},
|
||||||
|
ok: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await authAxios.get(
|
||||||
|
`/api/Issues/${this.$route.params.id}`
|
||||||
|
)
|
||||||
|
this.issue = response.data
|
||||||
|
const response2 = await axios.get('/api/IssueTypes')
|
||||||
|
response2.data.forEach((it) => {
|
||||||
|
this.issueTypes[it.id] = it
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async create() {
|
||||||
|
try {
|
||||||
|
this.problem.issueId = this.issue.id
|
||||||
|
await authAxios.post('/api/PublishedProblems', this.problem)
|
||||||
|
this.ok = 'Sukurtą.'
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route: 'fetchData',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
67
KTUSAPS/ClientApp/src/pages/Problems.vue
Normal file
67
KTUSAPS/ClientApp/src/pages/Problems.vue
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-success" v-if="ok">{{ ok }}</div>
|
||||||
|
<div class="alert alert-danger" v-if="error">{{ error }}</div>
|
||||||
|
<div class="card my-4" v-for="p in problems" :key="p.id">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="col-lg-2 col-md-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="h3">{{ votes[p.id] }}</span> aktualumo balas
|
||||||
|
<button @click="vote(p.id)" class="btn btn-primary">
|
||||||
|
Balsuoti
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg col-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-text text-muted">Paskelbta: {{ p.created }}</h6>
|
||||||
|
<h5 class="card-title">Problema:</h5>
|
||||||
|
<p class="card-text">{{ p.problemLt }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios, authAxios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
problems: [],
|
||||||
|
votes: {},
|
||||||
|
ok: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await axios.get('/api/PublishedProblems')
|
||||||
|
this.problems = response.data
|
||||||
|
this.problems.forEach((p) => {
|
||||||
|
this.fetchVoteCount(p.id)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async fetchVoteCount(id) {
|
||||||
|
const response = await axios.get(`/api/PublishedProblems/${id}/Votes`)
|
||||||
|
this.votes[id] = response.data
|
||||||
|
},
|
||||||
|
async vote(id) {
|
||||||
|
this.ok = null
|
||||||
|
this.error = null
|
||||||
|
try {
|
||||||
|
await authAxios.post(`/api/PublishedProblems/${id}/Votes`)
|
||||||
|
this.ok = 'Sekmingai prabalsuotą.'
|
||||||
|
await this.fetchVoteCount(id)
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
104
KTUSAPS/ClientApp/src/pages/Submit.vue
Normal file
104
KTUSAPS/ClientApp/src/pages/Submit.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container mt-2">
|
||||||
|
<div class="alert alert-danger" v-if="error">{{ error }}</div>
|
||||||
|
<div class="alert alert-success" v-if="ok">{{ ok }}</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="card col-lg-6 p-5">
|
||||||
|
<h1>Pateik savo problemą</h1>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="emailInput" class="form-label">El. paštas</label>
|
||||||
|
<input
|
||||||
|
class="form-control"
|
||||||
|
type="email"
|
||||||
|
id="emailInput"
|
||||||
|
:value="$store.state.msalAuth.email"
|
||||||
|
disabled
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="issueTypeSelect" class="form-label"
|
||||||
|
>Problemos tipas</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="issue.issueTypeId"
|
||||||
|
id="issueTypeSelect"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
|
<option v-for="it in issueTypes" :key="it.id" :value="it.id">
|
||||||
|
{{ it.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="descriptionTextArea" class="form-label">Aprašymas</label>
|
||||||
|
<textarea
|
||||||
|
v-model="issue.description"
|
||||||
|
class="form-control"
|
||||||
|
id="descriptionTextArea"
|
||||||
|
rows="4"
|
||||||
|
aria-describedby="descriptionHelp"
|
||||||
|
></textarea>
|
||||||
|
<div id="descriptionHelp" class="form-text">
|
||||||
|
Nepamirškite paminėti kokiame modulyje susiduriate su šią problemą.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="publishableCheckbox"
|
||||||
|
v-model="issue.publishable"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="publishable">
|
||||||
|
Ši problema turėtų būti viešai paskelbta, kad galėtų ją matyti kiti
|
||||||
|
studentai.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button @click="submit" class="btn btn-primary btn-lg">Pateikti</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { authAxios, axios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issueTypes: [],
|
||||||
|
issue: {
|
||||||
|
description: '',
|
||||||
|
issueTypeId: null,
|
||||||
|
publishable: false,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
ok: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await axios.get('/api/IssueTypes')
|
||||||
|
this.issueTypes = response.data
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
this.error = null
|
||||||
|
this.ok = null
|
||||||
|
try {
|
||||||
|
const response = await authAxios.post('/api/Issues', this.issue)
|
||||||
|
console.log(response)
|
||||||
|
this.ok = 'Problemą sekmingai pateikta.'
|
||||||
|
this.issue.description = ''
|
||||||
|
this.issue.publishable = false
|
||||||
|
this.issue.issueTypeId = null
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
15
KTUSAPS/ClientApp/src/pages/admin/Admin.vue
Normal file
15
KTUSAPS/ClientApp/src/pages/admin/Admin.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<b>Dėmesio!</b> Tu esi administracinėje skiltyje, visą informacija
|
||||||
|
pateikiama čia yra galimai konfidianciali.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="slide-fade" mode="out-in">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
62
KTUSAPS/ClientApp/src/pages/admin/IssueTypeEdit.vue
Normal file
62
KTUSAPS/ClientApp/src/pages/admin/IssueTypeEdit.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Redaguoti problemos tipą</h1>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="card col-lg-6 p-5">
|
||||||
|
<issue-type-form v-model="issueType" />
|
||||||
|
<alerter ref="submitAlerter" />
|
||||||
|
<button @click="update" class="btn btn-primary btn-lg">
|
||||||
|
Išsaugoti
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios, authAxios } from '@/axios'
|
||||||
|
import IssueTypeForm from '@/components/forms/IssueTypeForm.vue'
|
||||||
|
import Alerter from '@/components/Alerter.vue'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
IssueTypeForm,
|
||||||
|
Alerter,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issueType: { name: '', nameEn: '' },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/api/IssueTypes/${this.$route.params.id}`
|
||||||
|
)
|
||||||
|
this.issueType = response.data
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
try {
|
||||||
|
if (!this.issueType.name || !this.issueType.nameEn) {
|
||||||
|
this.$refs.submitAlerter.alertTimed({
|
||||||
|
text: 'Problemos tipas turi turėti pavadinimus!!!',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await authAxios.patch(
|
||||||
|
`/api/IssueTypes/${this.$route.params.id}`,
|
||||||
|
this.issueType
|
||||||
|
)
|
||||||
|
this.$router.push({ name: 'AdminIssueTypes' })
|
||||||
|
} catch (error) {
|
||||||
|
this.$refs.submitAlerter.alert({
|
||||||
|
title: 'Įvyko klaidą sukuriant tipą.',
|
||||||
|
text: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
54
KTUSAPS/ClientApp/src/pages/admin/IssueTypeNew.vue
Normal file
54
KTUSAPS/ClientApp/src/pages/admin/IssueTypeNew.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Naujas problemos tipas</h1>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="card col-lg-6 p-5">
|
||||||
|
<issue-type-form v-model="issueType" />
|
||||||
|
<alerter ref="submitAlerter" />
|
||||||
|
<button @click="create" class="btn btn-primary btn-lg">
|
||||||
|
Sukurti naują problemos tipą
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { authAxios } from '@/axios'
|
||||||
|
import IssueTypeForm from '@/components/forms/IssueTypeForm.vue'
|
||||||
|
import Alerter from '@/components/Alerter.vue'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
IssueTypeForm,
|
||||||
|
Alerter,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issueType: { name: '', nameEn: '' },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async create() {
|
||||||
|
try {
|
||||||
|
if (!this.issueType.name || !this.issueType.nameEn) {
|
||||||
|
this.$refs.submitAlerter.alertTimed({
|
||||||
|
text: 'Problemos tipas turi turėti pavadinimus!!!',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await authAxios.post('/api/IssueTypes', this.issueType)
|
||||||
|
this.$refs.submitAlerter.alertTimed({
|
||||||
|
text: 'Sėkmingai sukurtas problemos tipas.',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
this.issueType = {}
|
||||||
|
} catch (error) {
|
||||||
|
this.$refs.submitAlerter.alert({
|
||||||
|
title: 'Įvyko klaidą sukuriant tipą.',
|
||||||
|
text: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
57
KTUSAPS/ClientApp/src/pages/admin/IssueTypes.vue
Normal file
57
KTUSAPS/ClientApp/src/pages/admin/IssueTypes.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'AdminIssueTypeNew' }"
|
||||||
|
class="btn btn-primary mx-1"
|
||||||
|
>Sukurti naują problemos tipą</router-link
|
||||||
|
>
|
||||||
|
<div class="card my-4" v-for="it in issueTypes" :key="it.id">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ it.name }}</h5>
|
||||||
|
<h5 class="card-title text-muted">{{ it.nameEn }}</h5>
|
||||||
|
<button class="btn btn-danger mx-1" @click="deleteIssueType(it.id)">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
Ištrinti
|
||||||
|
</button>
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'AdminIssueTypeEdit', params: { id: it.id } }"
|
||||||
|
class="btn btn-primary mx-1"
|
||||||
|
>Redaguoti</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { axios, authAxios } from '@/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issueTypes: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchData() {
|
||||||
|
const response = await axios.get('/api/IssueTypes')
|
||||||
|
this.issueTypes = response.data
|
||||||
|
},
|
||||||
|
async deleteIssueType(id) {
|
||||||
|
try {
|
||||||
|
await this.$root.deleteConfirmation()
|
||||||
|
await authAxios.delete(`/api/IssueTypes/${id}`)
|
||||||
|
// TODO: Display some success info
|
||||||
|
await this.fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
// TODO: Display error.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
28
KTUSAPS/ClientApp/src/router/admin.js
Normal file
28
KTUSAPS/ClientApp/src/router/admin.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import IssueTypes from '@/pages/admin/IssueTypes.vue'
|
||||||
|
import IssueTypeNew from '@/pages/admin/IssueTypeNew.vue'
|
||||||
|
import IssueTypeEdit from '@/pages/admin/IssueTypeEdit.vue'
|
||||||
|
import Admin from '@/pages/admin/Admin.vue'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
component: Admin,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'issuetypes',
|
||||||
|
name: 'AdminIssueTypes',
|
||||||
|
component: IssueTypes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'issuetypes/new',
|
||||||
|
name: 'AdminIssueTypeNew',
|
||||||
|
component: IssueTypeNew,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'issuetypes/:id/edit',
|
||||||
|
name: 'AdminIssueTypeEdit',
|
||||||
|
component: IssueTypeEdit,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
55
KTUSAPS/ClientApp/src/router/index.js
Normal file
55
KTUSAPS/ClientApp/src/router/index.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { createWebHistory, createRouter } from 'vue-router'
|
||||||
|
import Home from '@/pages/Home.vue'
|
||||||
|
import Submit from '@/pages/Submit.vue'
|
||||||
|
import Problems from '@/pages/Problems.vue'
|
||||||
|
import Feedbacks from '@/pages/Feedbacks.vue'
|
||||||
|
import Issues from '@/pages/Issues.vue'
|
||||||
|
import NewProblem from '@/pages/NewProblem.vue'
|
||||||
|
import NewFeedback from '@/pages/NewFeedback.vue'
|
||||||
|
import AdminRoutes from './admin'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: Home,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/new',
|
||||||
|
name: 'Submit',
|
||||||
|
component: Submit,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/problems',
|
||||||
|
name: 'Problems',
|
||||||
|
component: Problems,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/feedbacks',
|
||||||
|
name: 'Feedbacks',
|
||||||
|
component: Feedbacks,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/issues',
|
||||||
|
name: 'Issues',
|
||||||
|
component: Issues,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/issues/:id/newProblem',
|
||||||
|
name: 'IssueNewProblem',
|
||||||
|
component: NewProblem,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/issues/:id/newFeedback',
|
||||||
|
name: 'IssueNewFeedback',
|
||||||
|
component: NewFeedback,
|
||||||
|
},
|
||||||
|
...AdminRoutes,
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
12
KTUSAPS/ClientApp/src/store/index.js
Normal file
12
KTUSAPS/ClientApp/src/store/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createStore, createLogger } from 'vuex'
|
||||||
|
import msalAuth from './modules/msalAuth'
|
||||||
|
|
||||||
|
const debug = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
modules: {
|
||||||
|
msalAuth,
|
||||||
|
},
|
||||||
|
strict: debug,
|
||||||
|
plugins: debug ? [createLogger()] : [],
|
||||||
|
})
|
77
KTUSAPS/ClientApp/src/store/modules/msalAuth.js
Normal file
77
KTUSAPS/ClientApp/src/store/modules/msalAuth.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { WatchMsalState, GetMsalState } from '@/msal'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
const state = () => ({
|
||||||
|
isReady: false,
|
||||||
|
isLoggedIn: false,
|
||||||
|
isAdmin: false,
|
||||||
|
accessToken: null,
|
||||||
|
idToken: null,
|
||||||
|
email: null,
|
||||||
|
displayName: null,
|
||||||
|
|
||||||
|
debugFullTokenResponse: null,
|
||||||
|
// debugAccountInfo: null,
|
||||||
|
onReady: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const getters = {}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
const actions = {
|
||||||
|
initialize({ commit }) {
|
||||||
|
WatchMsalState(async () => {
|
||||||
|
const state = GetMsalState()
|
||||||
|
let isAdmin = false
|
||||||
|
if (state.isLoggedIn && state.idToken) {
|
||||||
|
await axios
|
||||||
|
.get('/api/AuthMetadata/Admin', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${state.idToken}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => (isAdmin = res.data))
|
||||||
|
.catch((error) => console.error(error))
|
||||||
|
}
|
||||||
|
commit('setState', { ...state, isAdmin })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async waitTillReady({ commit, state }) {
|
||||||
|
if (!state.isReady) {
|
||||||
|
await new Promise((c) => {
|
||||||
|
commit('addReadyCalback', c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutations
|
||||||
|
const mutations = {
|
||||||
|
setState(state, msalState) {
|
||||||
|
state.isLoggedIn = msalState.isLoggedIn
|
||||||
|
state.isAdmin = msalState.isAdmin
|
||||||
|
state.accessToken = msalState.accessToken
|
||||||
|
state.idToken = msalState.idToken
|
||||||
|
state.debugFullTokenResponse = msalState.debugFullTokenResponse
|
||||||
|
// state.debugAccountInfo = msalState.debugAccountInfo
|
||||||
|
state.email = msalState.email
|
||||||
|
state.displayName = msalState.displayName
|
||||||
|
if (!state.isReady && state.idToken) {
|
||||||
|
state.isReady = true
|
||||||
|
state.onReady.forEach((c) => c())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addReadyCalback(state, callback) {
|
||||||
|
state.onReady.push(callback)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
}
|
13
KTUSAPS/ClientApp/vite.config.js
Normal file
13
KTUSAPS/ClientApp/vite.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
resolve:{
|
||||||
|
alias:{
|
||||||
|
'@' : path.resolve(__dirname, './src')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [vue()]
|
||||||
|
})
|
523
KTUSAPS/ClientApp/yarn.lock
Normal file
523
KTUSAPS/ClientApp/yarn.lock
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@azure/msal-browser@^2.21.0":
|
||||||
|
version "2.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.21.0.tgz#2ebe4b0874a61d64009419d4cfef1f6bb5e226b3"
|
||||||
|
integrity sha512-y+oAUJJ0m7gRUT505iGT4SDXktrRzOE3HZC0+nQyHDI+3O7BKK5NjQUwD/4V8FNNyrhxEtLk99iwJEo61epinQ==
|
||||||
|
dependencies:
|
||||||
|
"@azure/msal-common" "^6.0.0"
|
||||||
|
|
||||||
|
"@azure/msal-common@^6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-6.0.0.tgz#9a60c27967ae6f6678015eb6dfb0ea7cc999eb3b"
|
||||||
|
integrity sha512-Vva3snqmWPHJNDCBb4lz3D0rvZsi/0QikAxHvVFNwtNg5pP4NZE4U34tNiXN+m9KhlQFrZBPkE5rk7dIEOGcWw==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.1.1"
|
||||||
|
|
||||||
|
"@babel/parser@^7.16.4":
|
||||||
|
version "7.16.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.12.tgz#9474794f9a650cf5e2f892444227f98e28cdf8b6"
|
||||||
|
integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==
|
||||||
|
|
||||||
|
"@popperjs/core@^2.10.2":
|
||||||
|
version "2.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
|
||||||
|
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
|
||||||
|
|
||||||
|
"@vitejs/plugin-vue@^2.0.0":
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.1.0.tgz#ddf5e0059f84f2ff649afc25ce5a59211e670542"
|
||||||
|
integrity sha512-AZ78WxvFMYd8JmM/GBV6a6SGGTU0GgN/0/4T+FnMMsLzFEzTeAUwuraapy50ifHZsC+G5SvWs86bvaCPTneFlA==
|
||||||
|
|
||||||
|
"@vue/compiler-core@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097"
|
||||||
|
integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.16.4"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
estree-walker "^2.0.2"
|
||||||
|
source-map "^0.6.1"
|
||||||
|
|
||||||
|
"@vue/compiler-dom@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715"
|
||||||
|
integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ==
|
||||||
|
dependencies:
|
||||||
|
"@vue/compiler-core" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
|
||||||
|
"@vue/compiler-sfc@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead"
|
||||||
|
integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.16.4"
|
||||||
|
"@vue/compiler-core" "3.2.29"
|
||||||
|
"@vue/compiler-dom" "3.2.29"
|
||||||
|
"@vue/compiler-ssr" "3.2.29"
|
||||||
|
"@vue/reactivity-transform" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
estree-walker "^2.0.2"
|
||||||
|
magic-string "^0.25.7"
|
||||||
|
postcss "^8.1.10"
|
||||||
|
source-map "^0.6.1"
|
||||||
|
|
||||||
|
"@vue/compiler-ssr@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e"
|
||||||
|
integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA==
|
||||||
|
dependencies:
|
||||||
|
"@vue/compiler-dom" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
|
||||||
|
"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.0.0-beta.18":
|
||||||
|
version "6.0.0-beta.21.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.21.1.tgz#f1410f53c42aa67fa3b01ca7bdba891f69d7bc97"
|
||||||
|
integrity sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw==
|
||||||
|
|
||||||
|
"@vue/reactivity-transform@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354"
|
||||||
|
integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.16.4"
|
||||||
|
"@vue/compiler-core" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
estree-walker "^2.0.2"
|
||||||
|
magic-string "^0.25.7"
|
||||||
|
|
||||||
|
"@vue/reactivity@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.29.tgz#afdc9c111d4139b14600be17ad80267212af6052"
|
||||||
|
integrity sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g==
|
||||||
|
dependencies:
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
|
||||||
|
"@vue/runtime-core@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417"
|
||||||
|
integrity sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig==
|
||||||
|
dependencies:
|
||||||
|
"@vue/reactivity" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
|
||||||
|
"@vue/runtime-dom@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01"
|
||||||
|
integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA==
|
||||||
|
dependencies:
|
||||||
|
"@vue/runtime-core" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
csstype "^2.6.8"
|
||||||
|
|
||||||
|
"@vue/server-renderer@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102"
|
||||||
|
integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw==
|
||||||
|
dependencies:
|
||||||
|
"@vue/compiler-ssr" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
|
||||||
|
"@vue/shared@3.2.29":
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925"
|
||||||
|
integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw==
|
||||||
|
|
||||||
|
anymatch@~3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||||
|
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||||
|
dependencies:
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
picomatch "^2.0.4"
|
||||||
|
|
||||||
|
axios@^0.25.0:
|
||||||
|
version "0.25.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a"
|
||||||
|
integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.14.7"
|
||||||
|
|
||||||
|
binary-extensions@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
|
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||||
|
|
||||||
|
bootstrap-icons@^1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bootstrap-icons/-/bootstrap-icons-1.7.2.tgz#4024e081e2c850602552e1fed6451e682d09322a"
|
||||||
|
integrity sha512-NiR2PqC73AQOPdVSu6GJfnk+hN2z6powcistXk1JgPnKuoV2FSdSl26w931Oz9HYbKCcKUSB6ncZTYJAYJl3QQ==
|
||||||
|
|
||||||
|
bootstrap@^5.1.3:
|
||||||
|
version "5.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.1.3.tgz#ba081b0c130f810fa70900acbc1c6d3c28fa8f34"
|
||||||
|
integrity sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==
|
||||||
|
|
||||||
|
braces@~3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||||
|
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||||
|
dependencies:
|
||||||
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
|
"chokidar@>=3.0.0 <4.0.0":
|
||||||
|
version "3.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||||
|
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||||
|
dependencies:
|
||||||
|
anymatch "~3.1.2"
|
||||||
|
braces "~3.0.2"
|
||||||
|
glob-parent "~5.1.2"
|
||||||
|
is-binary-path "~2.1.0"
|
||||||
|
is-glob "~4.0.1"
|
||||||
|
normalize-path "~3.0.0"
|
||||||
|
readdirp "~3.6.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
csstype@^2.6.8:
|
||||||
|
version "2.6.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa"
|
||||||
|
integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==
|
||||||
|
|
||||||
|
debug@^4.1.1:
|
||||||
|
version "4.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
|
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
|
esbuild-android-arm64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44"
|
||||||
|
integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==
|
||||||
|
|
||||||
|
esbuild-darwin-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72"
|
||||||
|
integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==
|
||||||
|
|
||||||
|
esbuild-darwin-arm64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a"
|
||||||
|
integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==
|
||||||
|
|
||||||
|
esbuild-freebsd-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85"
|
||||||
|
integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==
|
||||||
|
|
||||||
|
esbuild-freebsd-arm64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52"
|
||||||
|
integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==
|
||||||
|
|
||||||
|
esbuild-linux-32@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69"
|
||||||
|
integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==
|
||||||
|
|
||||||
|
esbuild-linux-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3"
|
||||||
|
integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==
|
||||||
|
|
||||||
|
esbuild-linux-arm64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1"
|
||||||
|
integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==
|
||||||
|
|
||||||
|
esbuild-linux-arm@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe"
|
||||||
|
integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==
|
||||||
|
|
||||||
|
esbuild-linux-mips64le@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7"
|
||||||
|
integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==
|
||||||
|
|
||||||
|
esbuild-linux-ppc64le@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2"
|
||||||
|
integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==
|
||||||
|
|
||||||
|
esbuild-netbsd-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038"
|
||||||
|
integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==
|
||||||
|
|
||||||
|
esbuild-openbsd-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7"
|
||||||
|
integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==
|
||||||
|
|
||||||
|
esbuild-sunos-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4"
|
||||||
|
integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==
|
||||||
|
|
||||||
|
esbuild-windows-32@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7"
|
||||||
|
integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==
|
||||||
|
|
||||||
|
esbuild-windows-64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294"
|
||||||
|
integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==
|
||||||
|
|
||||||
|
esbuild-windows-arm64@0.13.15:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3"
|
||||||
|
integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==
|
||||||
|
|
||||||
|
esbuild@^0.13.12:
|
||||||
|
version "0.13.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf"
|
||||||
|
integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==
|
||||||
|
optionalDependencies:
|
||||||
|
esbuild-android-arm64 "0.13.15"
|
||||||
|
esbuild-darwin-64 "0.13.15"
|
||||||
|
esbuild-darwin-arm64 "0.13.15"
|
||||||
|
esbuild-freebsd-64 "0.13.15"
|
||||||
|
esbuild-freebsd-arm64 "0.13.15"
|
||||||
|
esbuild-linux-32 "0.13.15"
|
||||||
|
esbuild-linux-64 "0.13.15"
|
||||||
|
esbuild-linux-arm "0.13.15"
|
||||||
|
esbuild-linux-arm64 "0.13.15"
|
||||||
|
esbuild-linux-mips64le "0.13.15"
|
||||||
|
esbuild-linux-ppc64le "0.13.15"
|
||||||
|
esbuild-netbsd-64 "0.13.15"
|
||||||
|
esbuild-openbsd-64 "0.13.15"
|
||||||
|
esbuild-sunos-64 "0.13.15"
|
||||||
|
esbuild-windows-32 "0.13.15"
|
||||||
|
esbuild-windows-64 "0.13.15"
|
||||||
|
esbuild-windows-arm64 "0.13.15"
|
||||||
|
|
||||||
|
estree-walker@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||||
|
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||||
|
|
||||||
|
fill-range@^7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||||
|
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||||
|
dependencies:
|
||||||
|
to-regex-range "^5.0.1"
|
||||||
|
|
||||||
|
follow-redirects@^1.14.7:
|
||||||
|
version "1.14.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
|
||||||
|
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
|
||||||
|
|
||||||
|
fsevents@~2.3.2:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
|
|
||||||
|
function-bind@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
|
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||||
|
|
||||||
|
glob-parent@~5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
|
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||||
|
dependencies:
|
||||||
|
is-glob "^4.0.1"
|
||||||
|
|
||||||
|
has@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||||
|
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
|
immutable@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||||
|
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||||
|
|
||||||
|
is-binary-path@~2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||||
|
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||||
|
dependencies:
|
||||||
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
|
is-core-module@^2.8.1:
|
||||||
|
version "2.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
|
||||||
|
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
|
||||||
|
dependencies:
|
||||||
|
has "^1.0.3"
|
||||||
|
|
||||||
|
is-extglob@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||||
|
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||||
|
|
||||||
|
is-glob@^4.0.1, is-glob@~4.0.1:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
|
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||||
|
dependencies:
|
||||||
|
is-extglob "^2.1.1"
|
||||||
|
|
||||||
|
is-number@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||||
|
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||||
|
|
||||||
|
lodash@^4.17.21:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
|
magic-string@^0.25.7:
|
||||||
|
version "0.25.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||||
|
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||||
|
dependencies:
|
||||||
|
sourcemap-codec "^1.4.4"
|
||||||
|
|
||||||
|
ms@2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||||
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
|
nanoid@^3.1.30:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
|
||||||
|
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
|
||||||
|
|
||||||
|
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
|
|
||||||
|
path-parse@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
|
picocolors@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
|
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||||
|
|
||||||
|
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
|
postcss@^8.1.10, postcss@^8.4.5:
|
||||||
|
version "8.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
|
||||||
|
integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.1.30"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.0.1"
|
||||||
|
|
||||||
|
readdirp@~3.6.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||||
|
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||||
|
dependencies:
|
||||||
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
|
resolve@^1.20.0:
|
||||||
|
version "1.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
|
||||||
|
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
|
||||||
|
dependencies:
|
||||||
|
is-core-module "^2.8.1"
|
||||||
|
path-parse "^1.0.7"
|
||||||
|
supports-preserve-symlinks-flag "^1.0.0"
|
||||||
|
|
||||||
|
rollup@^2.59.0:
|
||||||
|
version "2.66.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.66.1.tgz#366b0404de353c4331d538c3ad2963934fcb4937"
|
||||||
|
integrity sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
sass@^1.49.0:
|
||||||
|
version "1.49.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.0.tgz#65ec1b1d9a6bc1bae8d2c9d4b392c13f5d32c078"
|
||||||
|
integrity sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==
|
||||||
|
dependencies:
|
||||||
|
chokidar ">=3.0.0 <4.0.0"
|
||||||
|
immutable "^4.0.0"
|
||||||
|
source-map-js ">=0.6.2 <2.0.0"
|
||||||
|
|
||||||
|
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
|
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||||
|
|
||||||
|
source-map@^0.6.1:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
|
sourcemap-codec@^1.4.4:
|
||||||
|
version "1.4.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||||
|
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
|
to-regex-range@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||||
|
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||||
|
dependencies:
|
||||||
|
is-number "^7.0.0"
|
||||||
|
|
||||||
|
vite@^2.7.2:
|
||||||
|
version "2.7.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite/-/vite-2.7.13.tgz#99b56e27dfb1e4399e407cf94648f5c7fb9d77f5"
|
||||||
|
integrity sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ==
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.13.12"
|
||||||
|
postcss "^8.4.5"
|
||||||
|
resolve "^1.20.0"
|
||||||
|
rollup "^2.59.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
vue-router@^4.0.12:
|
||||||
|
version "4.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.12.tgz#8dc792cddf5bb1abcc3908f9064136de7e13c460"
|
||||||
|
integrity sha512-CPXvfqe+mZLB1kBWssssTiWg4EQERyqJZes7USiqfW9B5N2x+nHlnsM1D3b5CaJ6qgCvMmYJnz+G0iWjNCvXrg==
|
||||||
|
dependencies:
|
||||||
|
"@vue/devtools-api" "^6.0.0-beta.18"
|
||||||
|
|
||||||
|
vue@^3.2.25:
|
||||||
|
version "3.2.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a"
|
||||||
|
integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q==
|
||||||
|
dependencies:
|
||||||
|
"@vue/compiler-dom" "3.2.29"
|
||||||
|
"@vue/compiler-sfc" "3.2.29"
|
||||||
|
"@vue/runtime-dom" "3.2.29"
|
||||||
|
"@vue/server-renderer" "3.2.29"
|
||||||
|
"@vue/shared" "3.2.29"
|
||||||
|
|
||||||
|
vuex@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vuex/-/vuex-4.0.2.tgz#f896dbd5bf2a0e963f00c67e9b610de749ccacc9"
|
||||||
|
integrity sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==
|
||||||
|
dependencies:
|
||||||
|
"@vue/devtools-api" "^6.0.0-beta.11"
|
58
KTUSAPS/Controllers/AuthMetadataController.cs
Normal file
58
KTUSAPS/Controllers/AuthMetadataController.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public class AuthMetadataController : ControllerBase
|
||||||
|
{
|
||||||
|
public class AuthMetadata
|
||||||
|
{
|
||||||
|
public string ClientId { get; set; }
|
||||||
|
public string Authority { get; set; }
|
||||||
|
public string Tenant { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
public AuthMetadataController(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get authethication metadata needed to obtain token.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet]
|
||||||
|
public AuthMetadata Index() => new AuthMetadata { ClientId = _configuration["ClientId"], Authority = _configuration["Authority"], Tenant = _configuration["Tenant"] };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true is provided token is valid, else throws exception
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <response code="200">Provided token is correct.</response>
|
||||||
|
/// <response code="401">No valid token provided.</response>
|
||||||
|
[Authorize]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[HttpGet("Authed")]
|
||||||
|
public bool IsAuthed() => true;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[HttpGet("Claims")]
|
||||||
|
public IEnumerable<object> Claims() => User.Claims.Select((c) => new { c.Type, c.Value });
|
||||||
|
|
||||||
|
[Authorize("admin")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[HttpGet("Admin")]
|
||||||
|
public bool IsAdmin() => true;
|
||||||
|
}
|
||||||
|
}
|
105
KTUSAPS/Controllers/IssueTypesController.cs
Normal file
105
KTUSAPS/Controllers/IssueTypesController.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using KTUSAPS.Data;
|
||||||
|
using KTUSAPS.Data.Model;
|
||||||
|
using KTUSAPS.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class IssueTypesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly SAPSDataContext _context;
|
||||||
|
|
||||||
|
public IssueTypesController(SAPSDataContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<IEnumerable<IssueType>>> GetIssueTypes()
|
||||||
|
{
|
||||||
|
return await _context.IssueTypes.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<ActionResult<IssueType>> CreateIssueType([FromBody] IssueType issueType)
|
||||||
|
{
|
||||||
|
if (issueType == null)
|
||||||
|
return BadRequest("No data provided for object to be created.");
|
||||||
|
if (issueType.Id != default)
|
||||||
|
return BadRequest("Id has been set on create request, please do not do that, set id to 0 or ommit it.");
|
||||||
|
if (issueType.Issues != null)
|
||||||
|
return BadRequest("Do not privide navigation property values.");
|
||||||
|
|
||||||
|
_context.IssueTypes.Add(issueType);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetIssueType), new { id = issueType.Id }, issueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/IssueTypes/5
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<IssueType>> GetIssueType(int id)
|
||||||
|
{
|
||||||
|
var issueType = await _context.IssueTypes.FindAsync(id);
|
||||||
|
|
||||||
|
if (issueType == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok(issueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<IActionResult> UpdateIssueType(int id, IssueType issueType)
|
||||||
|
{
|
||||||
|
var databaseIssueType = await _context.IssueTypes.FindAsync(id);
|
||||||
|
if (databaseIssueType == default)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var eIssueType = _context.Attach(databaseIssueType);
|
||||||
|
|
||||||
|
eIssueType.MovePropertyDataWhiteList(issueType, new string[] {
|
||||||
|
nameof(databaseIssueType.Name),
|
||||||
|
nameof(databaseIssueType.NameEn),
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(eIssueType.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<IActionResult> DeleteIssueType(int id)
|
||||||
|
{
|
||||||
|
var issueType = await _context.IssueTypes.FindAsync(id);
|
||||||
|
if (issueType == default)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.IssueTypes.Remove(issueType);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
KTUSAPS/Controllers/IssuesController.cs
Normal file
121
KTUSAPS/Controllers/IssuesController.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
using KTUSAPS.Auth;
|
||||||
|
using KTUSAPS.Data.Model;
|
||||||
|
using KTUSAPS.Extensions;
|
||||||
|
using KTUSAPS.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class IssuesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly Data.SAPSDataContext dataContext;
|
||||||
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
|
public IssuesController(Data.SAPSDataContext dataContext, IAuthorizationService authorizationService)
|
||||||
|
{
|
||||||
|
this.dataContext = dataContext;
|
||||||
|
_authorizationService = authorizationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<IEnumerable<Issue>>> GetIssues([FromQuery] RequestScope requestScope = RequestScope.All)
|
||||||
|
{
|
||||||
|
if (requestScope == RequestScope.All)
|
||||||
|
{
|
||||||
|
var authorizationResult = await _authorizationService.AuthorizeAsync(User, "admin");
|
||||||
|
if (!authorizationResult.Succeeded)
|
||||||
|
return Forbid();
|
||||||
|
return await dataContext.Issues.ToListAsync();
|
||||||
|
} else if (requestScope == RequestScope.My)
|
||||||
|
return await dataContext.Issues.Where(i => i.UserID == User.GetUserId()).ToListAsync();
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Issue>> CreateIssueAsync([FromBody] Issue issueToCreate)
|
||||||
|
{
|
||||||
|
if (issueToCreate == null)
|
||||||
|
return BadRequest("No data provided for object to be created.");
|
||||||
|
if (issueToCreate.Id != default)
|
||||||
|
return BadRequest("Id has been set on create request, please do not do that, set id to 0 or ommit it.");
|
||||||
|
if (issueToCreate.IssueTypeId == default)
|
||||||
|
return BadRequest("No typeId has been specified");
|
||||||
|
if (issueToCreate.Problem != null && issueToCreate.Feedback != null && issueToCreate.IssueType != null)
|
||||||
|
return BadRequest("Do not privide navigation property values.");
|
||||||
|
if (issueToCreate.UserID != default || issueToCreate.Email != default)
|
||||||
|
return BadRequest("Do not provide indentity values.");
|
||||||
|
|
||||||
|
issueToCreate.UserID = User.GetUserId();
|
||||||
|
issueToCreate.Email = User.GetEmail();
|
||||||
|
|
||||||
|
var createdValue = await dataContext.AddAsync(issueToCreate);
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetIssue), new { Id = createdValue.Entity.Id }, createdValue.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Issue>> GetIssue(int id)
|
||||||
|
{
|
||||||
|
var issue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
|
||||||
|
if(issue == default)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var authorizationResult = await _authorizationService.AuthorizeAsync(User, issue, new MyIssueRequirement());
|
||||||
|
if(authorizationResult.Succeeded)
|
||||||
|
return Ok(issue);
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<ActionResult<Issue>> UpdateIssueAsync(int id, [FromBody] Issue issue)
|
||||||
|
{
|
||||||
|
var databaseIssue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
|
||||||
|
if (databaseIssue == default)
|
||||||
|
return NotFound();
|
||||||
|
var eIssue = dataContext.Attach(databaseIssue);
|
||||||
|
eIssue.MovePropertyDataWhiteList(issue, new string[] {
|
||||||
|
nameof(databaseIssue.Description),
|
||||||
|
nameof(databaseIssue.IssueTypeId),
|
||||||
|
nameof(databaseIssue.Publishable)
|
||||||
|
});
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
return Ok(eIssue.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<IActionResult> DeleteIssueAsync(int id)
|
||||||
|
{
|
||||||
|
var issue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
|
||||||
|
if (issue == default)
|
||||||
|
return NotFound();
|
||||||
|
dataContext.Issues.Remove(issue);
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
KTUSAPS/Controllers/PublishedFeedbacksController.cs
Normal file
107
KTUSAPS/Controllers/PublishedFeedbacksController.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using KTUSAPS.Data;
|
||||||
|
using KTUSAPS.Data.Model;
|
||||||
|
using KTUSAPS.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class PublishedFeedbacksController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly SAPSDataContext _context;
|
||||||
|
|
||||||
|
public PublishedFeedbacksController(SAPSDataContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<IEnumerable<PublishedFeedback>>> GetPublishedFeedbacks()
|
||||||
|
{
|
||||||
|
return await _context.PublishedFeedbacks.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<ActionResult<PublishedFeedback>> PostPublishedFeedback(PublishedFeedback publishedFeedback)
|
||||||
|
{
|
||||||
|
if (publishedFeedback == null)
|
||||||
|
return BadRequest("No data provided for object to be created.");
|
||||||
|
if (publishedFeedback.Id != default)
|
||||||
|
return BadRequest("Id has been set on create request, please do not do that, set id to 0 or ommit it.");
|
||||||
|
if (publishedFeedback.Issue != null)
|
||||||
|
return BadRequest("Do not privide navigation property values.");
|
||||||
|
|
||||||
|
_context.PublishedFeedbacks.Add(publishedFeedback);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetPublishedFeedback), new { id = publishedFeedback.Id }, publishedFeedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<PublishedFeedback>> GetPublishedFeedback(int id)
|
||||||
|
{
|
||||||
|
var publishedFeedback = await _context.PublishedFeedbacks.FindAsync(id);
|
||||||
|
|
||||||
|
if (publishedFeedback == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return publishedFeedback;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<ActionResult<PublishedFeedback>> UpdatePublishedFeedback(int id, PublishedFeedback publishedFeedback)
|
||||||
|
{
|
||||||
|
var databasePublishedFeedback = await _context.PublishedFeedbacks.FindAsync(id);
|
||||||
|
if (databasePublishedFeedback == default)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var ePublishedFeedback = _context.Attach(databasePublishedFeedback);
|
||||||
|
|
||||||
|
ePublishedFeedback.MovePropertyDataWhiteList(publishedFeedback, new string[]
|
||||||
|
{
|
||||||
|
nameof(databasePublishedFeedback.FeedbackLt),
|
||||||
|
nameof(databasePublishedFeedback.FeedbackEn),
|
||||||
|
nameof(databasePublishedFeedback.Created),
|
||||||
|
nameof(databasePublishedFeedback.IssueId),
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(ePublishedFeedback.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<IActionResult> DeletePublishedFeedback(int id)
|
||||||
|
{
|
||||||
|
var publishedFeedback = await _context.PublishedFeedbacks.FindAsync(id);
|
||||||
|
if (publishedFeedback == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.PublishedFeedbacks.Remove(publishedFeedback);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
KTUSAPS/Controllers/PublishedProblemsController.cs
Normal file
168
KTUSAPS/Controllers/PublishedProblemsController.cs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using KTUSAPS.Data;
|
||||||
|
using KTUSAPS.Data.Model;
|
||||||
|
using KTUSAPS.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class PublishedProblemsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly SAPSDataContext _context;
|
||||||
|
|
||||||
|
public PublishedProblemsController(SAPSDataContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<IEnumerable<PublishedProblem>>> GetPublishedProblems()
|
||||||
|
{
|
||||||
|
return await _context.PublishedProblems.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<ActionResult<PublishedProblem>> CreatePublishedProblem([FromBody] PublishedProblem publishedProblem)
|
||||||
|
{
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return BadRequest("No data provided for object to be created.");
|
||||||
|
if (publishedProblem.Id != default)
|
||||||
|
return BadRequest("Id has been set on create request, please do not do that, set id to 0 or ommit it.");
|
||||||
|
if (publishedProblem.Issue != null || publishedProblem.Solution != null || publishedProblem.Votes != null)
|
||||||
|
return BadRequest("Do not provide navigation property values.");
|
||||||
|
|
||||||
|
_context.PublishedProblems.Add(publishedProblem);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetPublishedProblem), new { id = publishedProblem.Id }, publishedProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<PublishedProblem>> GetPublishedProblem(int id)
|
||||||
|
{
|
||||||
|
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return publishedProblem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<ActionResult<PublishedProblem>> UpdatePublishedProblem(int id, PublishedProblem publishedProblem)
|
||||||
|
{
|
||||||
|
var databasePublishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
if (databasePublishedProblem == default)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var ePublishedProblem = _context.Attach(databasePublishedProblem);
|
||||||
|
|
||||||
|
ePublishedProblem.MovePropertyDataWhiteList(publishedProblem, new string[]
|
||||||
|
{
|
||||||
|
nameof(databasePublishedProblem.ProblemLt),
|
||||||
|
nameof(databasePublishedProblem.ProblemEn),
|
||||||
|
nameof(databasePublishedProblem.Created),
|
||||||
|
nameof(databasePublishedProblem.IssueId),
|
||||||
|
nameof(databasePublishedProblem.SolutionId),
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(ePublishedProblem.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize("admin")]
|
||||||
|
public async Task<IActionResult> DeletePublishedProblem(int id)
|
||||||
|
{
|
||||||
|
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
_context.PublishedProblems.Remove(publishedProblem);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/Votes")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<int>> GetVoteCount(int id)
|
||||||
|
{
|
||||||
|
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return (await _context.Votes
|
||||||
|
.Where(v => v.ProblemId == publishedProblem.Id)
|
||||||
|
.ToListAsync())
|
||||||
|
.Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/Votes")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Vote>> Vote(int id)
|
||||||
|
{
|
||||||
|
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var vote = new Vote()
|
||||||
|
{
|
||||||
|
Problem = publishedProblem,
|
||||||
|
UserId = User.GetUserId(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Votes.Add(vote);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetVote), new { id = id, userId = vote.UserId }, vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id}/Votes/{userId}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Vote>> GetVote(int id, string userId)
|
||||||
|
{
|
||||||
|
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var vote = await _context.Votes
|
||||||
|
.Where(v => v.ProblemId == publishedProblem.Id && v.UserId == userId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (vote == default)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return vote;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
41
KTUSAPS/Extensions/ClaimsPrincipalExtensions.cs
Normal file
41
KTUSAPS/Extensions/ClaimsPrincipalExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Extensions
|
||||||
|
{
|
||||||
|
public static class ClaimsPrincipalExtensions
|
||||||
|
{
|
||||||
|
private static string getClaimValue(ClaimsPrincipal claimsPrincipal, string claimType)
|
||||||
|
{
|
||||||
|
return claimsPrincipal.Claims.FirstOrDefault(c => c.Type == claimType)?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetUserId(this ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
if (claimsPrincipal == null)
|
||||||
|
return null;
|
||||||
|
return getClaimValue(claimsPrincipal, ClaimTypes.NameIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetName(this ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
if (claimsPrincipal == null)
|
||||||
|
return null;
|
||||||
|
return getClaimValue(claimsPrincipal, "name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetEmail(this ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
if (claimsPrincipal == null)
|
||||||
|
return null;
|
||||||
|
return getClaimValue(claimsPrincipal, ClaimTypes.Email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetObjectId(this ClaimsPrincipal claimsPrincipal)
|
||||||
|
{
|
||||||
|
if (claimsPrincipal == null)
|
||||||
|
return null;
|
||||||
|
return getClaimValue(claimsPrincipal, "http://schemas.microsoft.com/identity/claims/objectidentifier");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
KTUSAPS/Extensions/EntityEntryExtensions.cs
Normal file
44
KTUSAPS/Extensions/EntityEntryExtensions.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Extensions
|
||||||
|
{
|
||||||
|
public static class EntityEntryExtensions
|
||||||
|
{
|
||||||
|
public static void MovePropertyDataBlackList(this EntityEntry target, object source, IEnumerable<string> blacklistedProprties)
|
||||||
|
{
|
||||||
|
MovePropertyData(target, source, (prop) => blacklistedProprties.Contains(prop.Metadata.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MovePropertyDataWhiteList(this EntityEntry target, object source, IEnumerable<string> whitelistedProprties)
|
||||||
|
{
|
||||||
|
MovePropertyData(target, source, (prop) => !whitelistedProprties.Contains(prop.Metadata.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MovePropertyData(this EntityEntry target, object source, Func<PropertyEntry, bool> isBlacklisted)
|
||||||
|
{
|
||||||
|
foreach (var prop in target.Properties)
|
||||||
|
{
|
||||||
|
if (isBlacklisted(prop))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var propertyInfo = prop.Metadata.PropertyInfo;
|
||||||
|
var newValue = propertyInfo.GetValue(source);
|
||||||
|
if (!newValue.isDefault()) {
|
||||||
|
prop.CurrentValue = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool isDefault(this object value)
|
||||||
|
{
|
||||||
|
if(value == default)
|
||||||
|
return true;
|
||||||
|
if (value is int || value is long)
|
||||||
|
return (int)value == default(int);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<RootNamespace>KTUSA_PS</RootNamespace>
|
<RootNamespace>KTUSAPS</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@@ -10,9 +10,25 @@
|
|||||||
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
|
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.10" />
|
||||||
<PackageReference Include="VueCliMiddleware" Version="3.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.12" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.6.3" />
|
||||||
|
<PackageReference Include="VueCliMiddleware" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -23,7 +39,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="ClientApp\src\assets\NewFile.txt" />
|
<ProjectReference Include="..\KTUSAPS.Data\KTUSAPS.Data.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
||||||
@@ -40,12 +56,10 @@
|
|||||||
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
|
<!-- 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 install" />
|
||||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
|
<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 -->
|
<!-- Include the newly-built files in the publish output -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
|
<DistFiles Include="$(SpaRoot)dist\**" />
|
||||||
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
|
|
||||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
8
KTUSAPS/Models/RequestScope.cs
Normal file
8
KTUSAPS/Models/RequestScope.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace KTUSAPS.Models
|
||||||
|
{
|
||||||
|
public enum RequestScope
|
||||||
|
{
|
||||||
|
All,
|
||||||
|
My,
|
||||||
|
}
|
||||||
|
}
|
@@ -7,7 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace KTUSA_PS
|
namespace KTUSAPS
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
@@ -16,10 +16,9 @@
|
|||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"KTUSA_PS": {
|
"KTUSAPS": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "weatherforecast",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
138
KTUSAPS/Services/DatabaseInitializationService.cs
Normal file
138
KTUSAPS/Services/DatabaseInitializationService.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using KTUSAPS.Data;
|
||||||
|
using KTUSAPS.Data.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace KTUSAPS.Services
|
||||||
|
{
|
||||||
|
public class DatabaseInitializationService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
private readonly ILogger<DatabaseInitializationService> logger;
|
||||||
|
|
||||||
|
public DatabaseInitializationService(IServiceProvider serviceProvider, ILogger<DatabaseInitializationService> logger)
|
||||||
|
{
|
||||||
|
this.serviceProvider = serviceProvider;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var scope = serviceProvider.CreateScope();
|
||||||
|
var dataContext = scope.ServiceProvider.GetRequiredService<SAPSDataContext>();
|
||||||
|
var migrations = (await dataContext.Database.GetPendingMigrationsAsync(cancellationToken: cancellationToken)).ToList();
|
||||||
|
if(migrations.Any())
|
||||||
|
{
|
||||||
|
logger.LogInformation($"There are {migrations.Count} pending migrations. Applying them");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await dataContext.Database.MigrateAsync(cancellationToken: cancellationToken);
|
||||||
|
await Seed(dataContext);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError("Migration failed. Database may be corrupt!");
|
||||||
|
logger.LogError(ex, "Migration failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Seed(SAPSDataContext dataContext)
|
||||||
|
{
|
||||||
|
var generalIssueType = await dataContext.IssueTypes.AddAsync(new IssueType()
|
||||||
|
{
|
||||||
|
Name = "Bendra",
|
||||||
|
NameEn = "General"
|
||||||
|
});
|
||||||
|
var otherIssueType = await dataContext.IssueTypes.AddAsync(new IssueType()
|
||||||
|
{
|
||||||
|
Name = "Kita",
|
||||||
|
NameEn = "Other"
|
||||||
|
});
|
||||||
|
var feedbackIssueType = await dataContext.IssueTypes.AddAsync(new IssueType()
|
||||||
|
{
|
||||||
|
Name = "Atsiliepimas",
|
||||||
|
NameEn = "Feedback"
|
||||||
|
});
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
var issue1 = await dataContext.Issues.AddAsync(new Issue()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now.AddDays(-5),
|
||||||
|
Description = "Man nepatinka dėstytojas.",
|
||||||
|
Email = "karolis.kundrotas@ktu.edu",
|
||||||
|
Publishable = true,
|
||||||
|
IssueType = generalIssueType.Entity
|
||||||
|
});
|
||||||
|
var issue2 = await dataContext.Issues.AddAsync(new Issue()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now.AddDays(-12).AddHours(3),
|
||||||
|
Description = "Dėtytoja atsiskaitymo metu leido nusirašynėti kitiems, o man neleido.",
|
||||||
|
Email = "karolis.kundrotas@ktu.edu",
|
||||||
|
Publishable = true,
|
||||||
|
IssueType = otherIssueType.Entity
|
||||||
|
});
|
||||||
|
var issue3 = await dataContext.Issues.AddAsync(new Issue()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now.AddDays(-18),
|
||||||
|
Description = "Tinklų destytoja per paskaitą neatsako į klausimus ir per paskaitą nieko neišmoko.",
|
||||||
|
Email = "karolis.kundrotas@ktu.edu",
|
||||||
|
Publishable = false,
|
||||||
|
IssueType = generalIssueType.Entity
|
||||||
|
});
|
||||||
|
var issue4 = await dataContext.Issues.AddAsync(new Issue()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now.AddDays(-18),
|
||||||
|
Description = "Saitynų destytojas Tomas labai maloniai ir profesonaliai bendrauja.",
|
||||||
|
Email = "karolis.kundrotas@ktu.edu",
|
||||||
|
Publishable = true,
|
||||||
|
IssueType = feedbackIssueType.Entity,
|
||||||
|
Solved = true
|
||||||
|
});
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
await dataContext.PublishedFeedbacks.AddAsync(new PublishedFeedback()
|
||||||
|
{
|
||||||
|
Issue = issue4.Entity,
|
||||||
|
FeedbackLt = "Studentas mano kad Saitynų dėstytojas Tomas yra profesonalus ir mandagiai bendraujantis.",
|
||||||
|
FeedbackEn = "Student thinks that Site creation module lecturer Tomas is profesonal ir pleasant at communications.",
|
||||||
|
});
|
||||||
|
|
||||||
|
var problem1 = await dataContext.PublishedProblems.AddAsync(new PublishedProblem()
|
||||||
|
{
|
||||||
|
Issue = issue2.Entity,
|
||||||
|
ProblemLt = "Atsikaitymo metu buvo leista nusirašynėti.",
|
||||||
|
ProblemEn = "During exam cheating was allowed.",
|
||||||
|
});
|
||||||
|
var problem2 = await dataContext.PublishedProblems.AddAsync(new PublishedProblem()
|
||||||
|
{
|
||||||
|
Issue = issue3.Entity,
|
||||||
|
ProblemLt = "Dėstytoja V. Pavardenė nemoko studentų per paskaitas, neraguoja į studentų klausimus, nesuteikia pagalbos.",
|
||||||
|
ProblemEn = "Lecturer V. Pavardenė does not lecture students, do not react to student questions and doesn't provide help."
|
||||||
|
});
|
||||||
|
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
await dataContext.Solutions.AddAsync(new Solution()
|
||||||
|
{
|
||||||
|
Problem = problem2.Entity,
|
||||||
|
SolutionLt = "V. Parvedenei buvo priskirta tarnybinę nuobauda.",
|
||||||
|
SolutionEn = ""
|
||||||
|
});
|
||||||
|
|
||||||
|
await dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
KTUSAPS/Startup.cs
Normal file
128
KTUSAPS/Startup.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using KTUSAPS.Auth;
|
||||||
|
using KTUSAPS.Services;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using VueCliMiddleware;
|
||||||
|
|
||||||
|
namespace KTUSAPS
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddControllers(options =>
|
||||||
|
options.SuppressAsyncSuffixInActionNames = false
|
||||||
|
)
|
||||||
|
.AddControllersAsServices();
|
||||||
|
services.AddSpaStaticFiles(configuration =>
|
||||||
|
{
|
||||||
|
configuration.RootPath = "ClientApp/dist";
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
|
.AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
options.MetadataAddress = "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0/.well-known/openid-configuration";
|
||||||
|
options.Audience = Configuration["ClientId"];
|
||||||
|
//options.Authority = Configuration["Authority"];
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddAuthorization((configure) =>
|
||||||
|
{
|
||||||
|
configure.DefaultPolicy = new AuthorizationPolicyBuilder()
|
||||||
|
.RequireAuthenticatedUser()
|
||||||
|
.Build();
|
||||||
|
configure.AddPolicy("admin", new AuthorizationPolicyBuilder(configure.DefaultPolicy)
|
||||||
|
.AddRequirements(new AdminRequirement())
|
||||||
|
.Build());
|
||||||
|
});
|
||||||
|
|
||||||
|
var connectionString = Configuration.GetConnectionString("Main");
|
||||||
|
services.AddDbContext<Data.SAPSDataContext>((options) => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
|
||||||
|
services.AddHostedService<DatabaseInitializationService>();
|
||||||
|
|
||||||
|
services.AddSingleton<IAuthorizationHandler, SaPsAuthorizationHandler>();
|
||||||
|
|
||||||
|
services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||||
|
options.AddSecurityDefinition("msad", new Microsoft.OpenApi.Models.OpenApiSecurityScheme()
|
||||||
|
{
|
||||||
|
Type = Microsoft.OpenApi.Models.SecuritySchemeType.OAuth2,
|
||||||
|
Flows = new Microsoft.OpenApi.Models.OpenApiOAuthFlows()
|
||||||
|
{
|
||||||
|
AuthorizationCode = new Microsoft.OpenApi.Models.OpenApiOAuthFlow()
|
||||||
|
{
|
||||||
|
AuthorizationUrl = new Uri("https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/oauth2/v2.0/authorize"),
|
||||||
|
TokenUrl = new Uri("https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/oauth2/v2.0/token"),
|
||||||
|
Scopes = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "openid", "Access to user's id" },
|
||||||
|
{ "profile", "Access to user's name" },
|
||||||
|
{ "email", "Access to email" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
|
||||||
|
app.UseSwaggerUI(c =>
|
||||||
|
{
|
||||||
|
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
|
||||||
|
|
||||||
|
c.OAuthClientId(Configuration["ClientId"]);
|
||||||
|
c.OAuthAppName("KTUSA Problem<65> sistema");
|
||||||
|
c.OAuthUsePkce();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseSwagger(c =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseSpaStaticFiles();
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapControllers();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseSpa(spa =>
|
||||||
|
{
|
||||||
|
spa.Options.SourcePath = "ClientApp/";
|
||||||
|
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
spa.UseVueCli(npmScript: "serve");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Main": "Server=localhost;User=saps_dev;Password=;Database=saps_dev"
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
@@ -6,7 +9,7 @@
|
|||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ClientId": "5931fda0-e9e0-4754-80c2-18bcb9d9561a",
|
"ClientId": "411a8715-3d63-4a03-9be8-9370d920e36f",
|
||||||
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
||||||
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
||||||
}
|
}
|
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Main": "Server=localhost;User=saps;Password=FuhcMfapPkGOH8DjPSAw;Database=saps"
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
@@ -7,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ClientId": "5931fda0-e9e0-4754-80c2-18bcb9d9561a",
|
"ClientId": "411a8715-3d63-4a03-9be8-9370d920e36f",
|
||||||
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
||||||
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
||||||
}
|
}
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright © 2025 Karolis K.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
167
README.md
Normal file
167
README.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# KTUSA Problemų Sistema (KTUSA-PS)
|
||||||
|
|
||||||
|
Status: Archived (development discontinued mid–2022)
|
||||||
|
|
||||||
|
## 1. Historical Note
|
||||||
|
Development began around 2020 as an initiative of the InfoSA academic committee. Due to a lack of continued institutional interest in deploying the platform, active work ceased around mid‑2022.
|
||||||
|
|
||||||
|
The creator (Karolis K.) previously collaborated with other InfoSA members on an earlier iteration of the system implemented with the Symfony PHP framework. This second iteration (the one in this repository) was a full rewrite using ASP.NET Core + Vue 3 and was developed solely by Karolis.
|
||||||
|
|
||||||
|
The information system (IS) was submitted in January 2022 as coursework for the university module:
|
||||||
|
T120B165 "Saityno taikomųjų programų projektavimas"
|
||||||
|
(https://uais.cr.ktu.lt/ktuis/stp_report_ects.mdl_ml?p_kodas=T120B165&p_year=2021&p_lang=LT&p_stp_id=8058)
|
||||||
|
|
||||||
|
No further functional evolution, security hardening, or production deployment steps were performed after the academic submission phase.
|
||||||
|
|
||||||
|
## 2. Overview
|
||||||
|
KTUSA-PS (KTU Student Association Problem System) is a web platform for registering, classifying, moderating, and publishing student issues and feedback. It was designed as a Single Page Application (SPA) with a REST API backend, emphasizing transparency in problem resolution workflows within the student community.
|
||||||
|
|
||||||
|
The original detailed Lithuanian system description remains in `SISTEMA.md`.
|
||||||
|
|
||||||
|
## 3. Technology Stack
|
||||||
|
### Backend (.NET)
|
||||||
|
- Framework: ASP.NET Core (C#)
|
||||||
|
- Data Access: Entity Framework Core (Code First)
|
||||||
|
- Database: MySQL
|
||||||
|
- Authentication & Authorization: Azure Active Directory (MSAL) + JWT Bearer
|
||||||
|
- API Documentation: Swagger / OpenAPI
|
||||||
|
- Pattern: MVC + separated data layer project (`KTUSAPS.Data`)
|
||||||
|
|
||||||
|
### Frontend (Vue.js)
|
||||||
|
- Framework: Vue.js 3 (Composition API)
|
||||||
|
- Router: Vue Router 4
|
||||||
|
- State Management: Vuex 4
|
||||||
|
- UI: Bootstrap 5 + Bootstrap Icons
|
||||||
|
- Build Tool: Vite
|
||||||
|
- HTTP Client: Axios
|
||||||
|
- Authentication: MSAL Browser library (Azure AD integration)
|
||||||
|
|
||||||
|
## 4. Core Functional Domains
|
||||||
|
### For Students
|
||||||
|
1. Submit issues / problems
|
||||||
|
2. Choose issue types (categorization)
|
||||||
|
3. Provide feedback / comments and votes
|
||||||
|
|
||||||
|
### For Administrators
|
||||||
|
1. Manage issues (review, update status, resolve)
|
||||||
|
2. Maintain issue types (create / edit bilingual categories)
|
||||||
|
3. Control publication of problems & solutions
|
||||||
|
4. Moderate published feedback
|
||||||
|
|
||||||
|
## 5. Data Model (Key Entities)
|
||||||
|
- Issue – core submitted problem (type, description, resolution state)
|
||||||
|
- IssueType – bilingual (LT/EN) classification metadata
|
||||||
|
- PublishedProblem – publicly visible version of an issue
|
||||||
|
- PublishedFeedback – publicly visible feedback / commentary
|
||||||
|
- Admin – administrator role assignments
|
||||||
|
- Vote – rating / voting entity for feedback or solutions
|
||||||
|
|
||||||
|
## 6. Security Features (As Implemented)
|
||||||
|
1. Azure AD authentication (institutional accounts)
|
||||||
|
2. Role / claim-based authorization for admin-only areas
|
||||||
|
3. JWT validation for API access control
|
||||||
|
4. HTTPS-first assumptions (no explicit production reverse-proxy hardening done)
|
||||||
|
5. Localized security warnings (e.g., unsafe context notices)
|
||||||
|
|
||||||
|
Note: Since the system was never hardened for production, additional measures (rate limiting, auditing, CSP, advanced logging, backup strategy) were not finalized.
|
||||||
|
|
||||||
|
## 7. Architecture Highlights
|
||||||
|
- SPA frontend served alongside ASP.NET Core backend
|
||||||
|
- RESTful API boundaries; clear separation of concerns
|
||||||
|
- Separate data project (`KTUSAPS.Data`) to isolate Entity Framework models & migrations
|
||||||
|
- Bilingual support (Lithuanian / English) for selected entities
|
||||||
|
- Responsive UI leveraging Bootstrap 5
|
||||||
|
|
||||||
|
## 8. Project Structure (Simplified)
|
||||||
|
```
|
||||||
|
KTUSA PS.sln
|
||||||
|
├── KTUSAPS/ # Web application (API + SPA host)
|
||||||
|
│ ├── Controllers/ # API endpoints
|
||||||
|
│ ├── Auth/ # Authorization handlers & policies
|
||||||
|
│ ├── Services/ # (Planned/initial) business services
|
||||||
|
│ ├── ClientApp/ # Vue 3 application (Vite)
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── components/
|
||||||
|
│ │ │ ├── pages/
|
||||||
|
│ │ │ ├── router/
|
||||||
|
│ │ │ └── store/
|
||||||
|
│ │ ├── package.json
|
||||||
|
│ │ └── vite.config.js
|
||||||
|
│ ├── Startup.cs / Program.cs
|
||||||
|
│ └── appsettings*.json
|
||||||
|
└── KTUSAPS.Data/ # Data layer (DbContext + EF models + migrations)
|
||||||
|
├── Model/
|
||||||
|
├── Migrations/
|
||||||
|
└── SAPSDataContext.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Getting Started (Historical Dev Setup)
|
||||||
|
Prerequisites:
|
||||||
|
- .NET 6 SDK
|
||||||
|
- Node.js 16+ (with npm)
|
||||||
|
- MySQL Server (local or remote)
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
```bash
|
||||||
|
cd KTUSAPS
|
||||||
|
dotnet restore
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
The API will start on the configured Kestrel port (see `launchSettings.json`).
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
```bash
|
||||||
|
cd KTUSAPS/ClientApp
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
Vite dev server will proxy or call the API directly (adjust base URLs in Axios / config if needed).
|
||||||
|
|
||||||
|
### Database
|
||||||
|
1. Create a MySQL database (e.g., `ktusaps_db`).
|
||||||
|
2. Update the connection string in `KTUSAPS/appsettings.Development.json` (or `appsettings.json`).
|
||||||
|
3. Apply migrations:
|
||||||
|
```bash
|
||||||
|
cd KTUSAPS
|
||||||
|
dotnet ef database update
|
||||||
|
```
|
||||||
|
|
||||||
|
If `dotnet ef` is not available, install tools: `dotnet tool install --global dotnet-ef`.
|
||||||
|
|
||||||
|
## 10. Authentication Configuration (Azure AD)
|
||||||
|
The project expects Azure AD application credentials (Tenant ID, Client ID, etc.) in configuration. Since the environment is no longer active, placeholders may exist. For reenabling:
|
||||||
|
- Register an app in Azure AD
|
||||||
|
- Configure redirect URIs for SPA & API
|
||||||
|
- Populate settings (e.g., `AzureAd` section) in `appsettings.Development.json`
|
||||||
|
|
||||||
|
## 11. Usage Flow (Conceptual)
|
||||||
|
1. User authenticates via Azure AD popup (MSAL in browser).
|
||||||
|
2. Access token is obtained and attached to API calls (Bearer token).
|
||||||
|
3. Student submits an issue (Issue stored; optionally flagged for publication later).
|
||||||
|
4. Admin reviews issues, assigns type, updates status.
|
||||||
|
5. Issue may be published (becomes `PublishedProblem`).
|
||||||
|
6. Feedback & votes are optionally published (`PublishedFeedback`).
|
||||||
|
|
||||||
|
## 12. Limitations / Incomplete Areas
|
||||||
|
- No production-grade logging & monitoring strategy
|
||||||
|
- Limited error handling / validation messages
|
||||||
|
- No automated CI/CD pipeline included
|
||||||
|
- Security hardening (headers, rate limiting, input sanitation depth) incomplete
|
||||||
|
- i18n only partial beyond data fields (UI translations not fully finalized)
|
||||||
|
- Testing (unit/integration) minimal to none in repository
|
||||||
|
|
||||||
|
## 13. Academic Context
|
||||||
|
This codebase primarily served as an academic / portfolio artifact rather than a deployed organizational system. It demonstrates full-stack integration (Azure AD auth, EF Core, SPA frontend) rather than polished end-user production readiness.
|
||||||
|
|
||||||
|
## 14. Contribution & Maintenance
|
||||||
|
This repository is unmaintained. Pull requests are unlikely to be reviewed. Fork if you wish to build upon it. Consider upgrading dependencies (ASP.NET Core version, Vue tooling, MSAL libraries) before any reuse.
|
||||||
|
|
||||||
|
## 15. License
|
||||||
|
Released under the MIT License. See the `LICENSE` file for the complete text and copyright notice.
|
||||||
|
|
||||||
|
## 16. Credits
|
||||||
|
Author: Karolis K.
|
||||||
|
Initial concept support: InfoSA academic committee (earlier iteration collaboration).
|
||||||
|
|
||||||
|
---
|
||||||
|
For the original Lithuanian descriptive document, see `SISTEMA.md`.
|
150
SISTEMA.md
Normal file
150
SISTEMA.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# KTUSA Problemų Sistema (KTUSA-PS)
|
||||||
|
|
||||||
|
## Apžvalga
|
||||||
|
|
||||||
|
**KTUSA-PS** yra **KTU SA (Kauno technologijos universiteto Studentų atstovybės) problemų sprendimo sistema** – internetinė platforma, skirta studentų problemų registravimui, tvarkymui ir sprendimui.
|
||||||
|
|
||||||
|
## Technologijų rinkinys
|
||||||
|
|
||||||
|
### Backend (.NET)
|
||||||
|
- **Framework**: ASP.NET Core (C#)
|
||||||
|
- **Duomenų bazė**: MySQL su Entity Framework Core
|
||||||
|
- **Autentifikacija**: Microsoft Azure Active Directory (MSAL) su JWT Bearer autentifikacija
|
||||||
|
- **API**: RESTful API su Swagger dokumentacija
|
||||||
|
- **Architektūra**: MVC pattern su atskirtu duomenų sluoksniu
|
||||||
|
|
||||||
|
### Frontend (Vue.js)
|
||||||
|
- **Framework**: Vue.js 3 su Composition API
|
||||||
|
- **Maršrutizacija**: Vue Router 4
|
||||||
|
- **Būsenos valdymas**: Vuex 4
|
||||||
|
- **UI Framework**: Bootstrap 5 su Bootstrap Icons
|
||||||
|
- **Build Tool**: Vite
|
||||||
|
- **HTTP klientas**: Axios
|
||||||
|
- **Autentifikacija**: Azure MSAL Browser
|
||||||
|
|
||||||
|
## Pagrindinės funkcijos
|
||||||
|
|
||||||
|
### Studentams
|
||||||
|
1. **Problemų registravimas** – studentai gali pateikti problemas sistemoje
|
||||||
|
2. **Problemų tipų pasirinkimas** – galimybė klasifikuoti problemas pagal tipus
|
||||||
|
3. **Atsiliepimų teikimas** – galimybė komentuoti ir vertinti sprendimus
|
||||||
|
|
||||||
|
### Administratoriams
|
||||||
|
1. **Problemų valdymas** – peržiūrėti, redaguoti ir spręsti pateiktas problemas
|
||||||
|
2. **Problemų tipų administravimas** – kurti ir tvarkyti problemų kategorijas
|
||||||
|
3. **Publikavimo kontrolė** – spręsti, kurios problemos ir sprendimai bus viešai matomi
|
||||||
|
4. **Atsiliepimų moderavimas** – tvarkyti naudotojų atsiliepimus
|
||||||
|
|
||||||
|
## Duomenų modelis
|
||||||
|
|
||||||
|
### Pagrindinės esybės
|
||||||
|
- **Issue** – problemų registravimas su aprašymu, tipu ir sprendimo būsena
|
||||||
|
- **IssueType** – problemų kategorijos (dvikalbės: LT/EN)
|
||||||
|
- **PublishedProblem** – viešai publikuojamos problemos
|
||||||
|
- **PublishedFeedback** – viešai publikuojami atsiliepimai
|
||||||
|
- **Admin** – administratorių vaidmenys
|
||||||
|
- **Vote** – balsavimo / vertinimo sistema
|
||||||
|
|
||||||
|
## Saugumo funkcijos
|
||||||
|
|
||||||
|
1. **Azure AD integracija** – autentifikacija per Microsoft Active Directory
|
||||||
|
2. **Vaidmenų sistema** – atskirtos administratorių ir paprastų naudotojų teisės
|
||||||
|
3. **JWT tokenų validacija** – saugus API prieigos kontrolės mechanizmas
|
||||||
|
4. **HTTPS privalomumas** – saugus duomenų perdavimas
|
||||||
|
5. **Lokalizuoti saugumo perspėjimai** – naudotojų informavimas apie nesaugius ryšius
|
||||||
|
|
||||||
|
## Architektūros ypatybės
|
||||||
|
|
||||||
|
- **SPA (Single Page Application)** – Vue.js kliento dalis su ASP.NET Core serverio dalimi
|
||||||
|
- **RESTful API** – aiškiai struktūrizuotas API dizainas
|
||||||
|
- **Mikroservisų elementai** – atskirtas duomenų sluoksnis (`KTUSAPS.Data`)
|
||||||
|
- **Lokalizacija** – dvi kalbos (lietuvių ir anglų)
|
||||||
|
- **Prisitaikantis dizainas** – sukurtas su Bootstrap, pritaikytas mobiliesiems įrenginiams
|
||||||
|
|
||||||
|
## Projekto struktūra
|
||||||
|
|
||||||
|
```
|
||||||
|
KTUSA PS.sln # Visual Studio solution failas
|
||||||
|
├── KTUSAPS/ # Pagrindinis web aplikacijos projektas
|
||||||
|
│ ├── Controllers/ # API kontroleriai
|
||||||
|
│ │ ├── IssuesController.cs # Problemų valdymo API
|
||||||
|
│ │ ├── IssueTypesController.cs # Problemų tipų API
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── ClientApp/ # Vue.js frontend aplikacija
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── components/ # Vue komponentai
|
||||||
|
│ │ │ ├── pages/ # Puslapių komponentai
|
||||||
|
│ │ │ ├── router/ # Maršrutizacijos konfigūracija
|
||||||
|
│ │ │ └── store/ # Vuex būsenos valdymas
|
||||||
|
│ │ ├── package.json
|
||||||
|
│ │ └── vite.config.js
|
||||||
|
│ ├── Auth/ # Autentifikacijos logika
|
||||||
|
│ ├── Services/ # Verslo logikos servisai
|
||||||
|
│ └── Program.cs # Aplikacijos entry point
|
||||||
|
└── KTUSAPS.Data/ # Duomenų sluoksnis
|
||||||
|
├── Model/ # Entity Framework modeliai
|
||||||
|
├── Migrations/ # Duomenų bazės migracijos
|
||||||
|
└── SAPSDataContext.cs # DbContext
|
||||||
|
```
|
||||||
|
|
||||||
|
## Diegimas ir konfigūracija
|
||||||
|
|
||||||
|
- **Konfigūracijos failai** – `appsettings.json` (taip pat `appsettings.Development.json` vystymui)
|
||||||
|
- **Parengta naudoti su Docker** – tiek Vite, tiek .NET Core dalims
|
||||||
|
- **Duomenų bazės migracijos** – valdomos per Entity Framework Core migracijas
|
||||||
|
- **Swagger dokumentacija** – automatiškai generuojama API dokumentacija
|
||||||
|
|
||||||
|
## Paleidimo instrukcijos
|
||||||
|
|
||||||
|
### Reikalavimai
|
||||||
|
- .NET 6.0 arba naujesnis
|
||||||
|
- Node.js 16+ ir npm
|
||||||
|
- MySQL serveris
|
||||||
|
|
||||||
|
### Serverio dalies paleidimas
|
||||||
|
```bash
|
||||||
|
cd KTUSAPS
|
||||||
|
dotnet restore
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kliento dalies paleidimas
|
||||||
|
```bash
|
||||||
|
cd KTUSAPS/ClientApp
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Duomenų bazės konfigūracija
|
||||||
|
1. Sukurkite MySQL duomenų bazę
|
||||||
|
2. Atnaujinkite prisijungimo eilutę (connection string) faile `appsettings.json`
|
||||||
|
3. Paleiskite migracijas:
|
||||||
|
```bash
|
||||||
|
dotnet ef database update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pagrindinės sistemos dalys
|
||||||
|
|
||||||
|
### Autentifikacija
|
||||||
|
Sistema naudoja Microsoft Azure Active Directory autentifikaciją. Vartotojai prisijungia per savo institucijos paskyrą, o sistema automatiškai nustato jų teises.
|
||||||
|
|
||||||
|
### Problemų valdymas
|
||||||
|
- Studentai gali registruoti problemas per intuityvią formą
|
||||||
|
- Administratoriai gali peržiūrėti visas problemas ir jas administruoti
|
||||||
|
- Problemos gali būti skirstomos į tipus ir kategorijas
|
||||||
|
|
||||||
|
### Publikavimo sistema
|
||||||
|
- Administratoriai sprendžia, kurios problemos ir sprendimai bus viešai matomi
|
||||||
|
- Publikuotos problemos tampa matomos visiems sistemos naudotojams
|
||||||
|
- Atsiliepimų sistema leidžia vertinti sprendimų kokybę
|
||||||
|
|
||||||
|
## Saugumas ir privatumas
|
||||||
|
|
||||||
|
- Visi duomenys perduodami per HTTPS
|
||||||
|
- Vartotojų duomenys saugomi pagal GDPR reikalavimus
|
||||||
|
- Prieiga prie administratoriaus funkcijų griežtai kontroliuojama
|
||||||
|
- Sistema perspėja apie nesaugius ryšius ar lokalų paleidimą
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Sistema sukurta siekiant pagerinti KTU studentų problemų sprendimo procesus, užtikrinant skaidrumą, efektyvumą ir saugumą visame problemų valdymo cikle.
|
Reference in New Issue
Block a user