Compare commits
10 Commits
257308e096
...
8f4c0688f7
Author | SHA1 | Date | |
---|---|---|---|
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
|
||||
VisualStudioVersion = 17.0.31606.5
|
||||
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
|
||||
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
|
||||
Global
|
||||
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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace KTUSAPS.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(SAPSDataContext))]
|
||||
[Migration("20210909173149_Initial")]
|
||||
[Migration("20211015122630_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -50,6 +50,9 @@ namespace KTUSAPS.Data.Migrations
|
||||
.HasMaxLength(320)
|
||||
.HasColumnType("varchar(320)");
|
||||
|
||||
b.Property<int>("IssueTypeId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Publishable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -62,9 +65,28 @@ namespace KTUSAPS.Data.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueTypeId");
|
||||
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -169,6 +191,17 @@ namespace KTUSAPS.Data.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("KTUSAPS.Data.Model.Issue", "Issue")
|
||||
@@ -211,6 +244,11 @@ namespace KTUSAPS.Data.Migrations
|
||||
b.Navigation("Problem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KTUSAPS.Data.Model.IssueType", b =>
|
||||
{
|
||||
b.Navigation("Issues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedProblem", b =>
|
||||
{
|
||||
b.Navigation("Votes");
|
@@ -27,24 +27,19 @@ namespace KTUSAPS.Data.Migrations
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Issues",
|
||||
name: "IssueTypes",
|
||||
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)
|
||||
Name = table.Column<string>(type: "longtext", 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)
|
||||
NameEn = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Issues", x => x.Id);
|
||||
table.PrimaryKey("PK_IssueTypes", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
@@ -66,6 +61,35 @@ namespace KTUSAPS.Data.Migrations
|
||||
})
|
||||
.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(
|
||||
name: "PublishedFeedbacks",
|
||||
columns: table => new
|
||||
@@ -147,6 +171,11 @@ namespace KTUSAPS.Data.Migrations
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Issues_IssueTypeId",
|
||||
table: "Issues",
|
||||
column: "IssueTypeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PublishedFeedbacks_IssueId",
|
||||
table: "PublishedFeedbacks",
|
||||
@@ -190,6 +219,9 @@ namespace KTUSAPS.Data.Migrations
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Solutions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IssueTypes");
|
||||
}
|
||||
}
|
||||
}
|
@@ -48,6 +48,9 @@ namespace KTUSAPS.Data.Migrations
|
||||
.HasMaxLength(320)
|
||||
.HasColumnType("varchar(320)");
|
||||
|
||||
b.Property<int>("IssueTypeId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("Publishable")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
@@ -60,9 +63,28 @@ namespace KTUSAPS.Data.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueTypeId");
|
||||
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -167,6 +189,17 @@ namespace KTUSAPS.Data.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("KTUSAPS.Data.Model.Issue", "Issue")
|
||||
@@ -209,6 +242,11 @@ namespace KTUSAPS.Data.Migrations
|
||||
b.Navigation("Problem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KTUSAPS.Data.Model.IssueType", b =>
|
||||
{
|
||||
b.Navigation("Issues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KTUSAPS.Data.Model.PublishedProblem", b =>
|
||||
{
|
||||
b.Navigation("Votes");
|
||||
|
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KTUSAPS.Data.Model
|
||||
@@ -22,7 +23,17 @@ namespace KTUSAPS.Data.Model
|
||||
[MaxLength]
|
||||
public string Description { get; set; }
|
||||
|
||||
public PublishedProblem Problem { get; set; }
|
||||
public PublishedFeedback Feedback { get; set; }
|
||||
public int IssueTypeId { 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"?>
|
||||
<ClassDiagram MajorVersion="1" MinorVersion="1">
|
||||
<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>
|
||||
<HashCode>AAACAAJAACAgAAAAAAACAAgIAAAAAAQAAAAAAAAAAAA=</HashCode>
|
||||
<HashCode>AIACAAJQACAgAAAAAAACAAgIAAAAAAQAAAAAAAAAAAA=</HashCode>
|
||||
<FileName>Model\Issue.cs</FileName>
|
||||
</TypeIdentifier>
|
||||
<ShowAsAssociation>
|
||||
<Property Name="IssueType" />
|
||||
<Property Name="Problem" />
|
||||
<Property Name="Feedback" />
|
||||
</ShowAsAssociation>
|
||||
</Class>
|
||||
<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>
|
||||
<HashCode>AAECAAAAAAAAAAAACAIAAAAAAAAAAAQAEAAAAAAAAAA=</HashCode>
|
||||
<FileName>Model\PublishedFeedback.cs</FileName>
|
||||
@@ -22,9 +23,9 @@
|
||||
</ShowAsAssociation>
|
||||
</Class>
|
||||
<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>
|
||||
<HashCode>ACECABAAAAEAAAgAAAABAAAAEAAAQAQAEAAAAAAAAAA=</HashCode>
|
||||
<HashCode>ACECABAAAAEAAAgAAAABAAAAAAAAAAQAEAAAAAAAAAA=</HashCode>
|
||||
<FileName>Model\PublishedProblem.cs</FileName>
|
||||
</TypeIdentifier>
|
||||
<ShowAsAssociation>
|
||||
@@ -36,7 +37,7 @@
|
||||
</ShowAsCollectionAssociation>
|
||||
</Class>
|
||||
<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>
|
||||
<HashCode>AAACAAIABAAAAQAAAAAAAAAAAAAAAAQAAAAAAAAAAAA=</HashCode>
|
||||
<FileName>Model\Solution.cs</FileName>
|
||||
@@ -46,7 +47,7 @@
|
||||
</ShowAsAssociation>
|
||||
</Class>
|
||||
<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>
|
||||
<HashCode>AAAAAAIAAAAAEAAAAAAAAAgAAAAAAAAAAAAAAAAAAAA=</HashCode>
|
||||
<FileName>Model\Vote.cs</FileName>
|
||||
@@ -55,5 +56,22 @@
|
||||
<Property Name="Problem" />
|
||||
</ShowAsAssociation>
|
||||
</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" />
|
||||
</ClassDiagram>
|
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KTUSAPS.Data.Model
|
||||
@@ -22,6 +23,12 @@ namespace KTUSAPS.Data.Model
|
||||
public DateTime Created { 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.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KTUSAPS.Data.Model
|
||||
@@ -18,20 +19,22 @@ namespace KTUSAPS.Data.Model
|
||||
[Required]
|
||||
[MaxLength]
|
||||
public string ProblemEn { get; set; }
|
||||
[MaxLength]
|
||||
public string ResponseLt { get; set; }
|
||||
[MaxLength]
|
||||
public string ResponseEn { get; set; }
|
||||
|
||||
public DateTime Created { 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 Solution Solution { get; set; }
|
||||
public ICollection<Vote> Votes { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
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.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace KTUSAPS.Data.Model
|
||||
{
|
||||
@@ -13,8 +14,13 @@ namespace KTUSAPS.Data.Model
|
||||
[MaxLength]
|
||||
public string SolutionEn { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual PublishedProblem Problem { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public PublishedProblem Problem { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public Solution()
|
||||
{
|
||||
Created = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace KTUSAPS.Data.Model
|
||||
@@ -12,7 +13,8 @@ namespace KTUSAPS.Data.Model
|
||||
[MaxLength(64)]
|
||||
public string UserId { 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<PublishedFeedback> PublishedFeedbacks { 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 './assets/main.scss'
|
||||
import 'bootstrap'
|
||||
import './msal'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@@ -11,5 +12,3 @@ app.use(router)
|
||||
app.use(store)
|
||||
|
||||
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>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>KTUSA_PS</RootNamespace>
|
||||
<RootNamespace>KTUSAPS</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -10,9 +10,25 @@
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IncludeOpenAPIAnalyzers>true</IncludeOpenAPIAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.8" />
|
||||
<PackageReference Include="VueCliMiddleware" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.10" />
|
||||
<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>
|
||||
@@ -23,7 +39,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="ClientApp\src\assets\NewFile.txt" />
|
||||
<ProjectReference Include="..\KTUSAPS.Data\KTUSAPS.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<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 -->
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
|
||||
|
||||
<!-- Include the newly-built files in the publish output -->
|
||||
<ItemGroup>
|
||||
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
|
||||
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
|
||||
<DistFiles Include="$(SpaRoot)dist\**" />
|
||||
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>%(DistFiles.Identity)</RelativePath>
|
||||
<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.Threading.Tasks;
|
||||
|
||||
namespace KTUSA_PS
|
||||
namespace KTUSAPS
|
||||
{
|
||||
public class Program
|
||||
{
|
@@ -16,10 +16,9 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"KTUSA_PS": {
|
||||
"KTUSAPS": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "weatherforecast",
|
||||
"environmentVariables": {
|
||||
"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": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
@@ -6,7 +9,7 @@
|
||||
"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",
|
||||
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
||||
}
|
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"Main": "Server=localhost;User=saps;Password=FuhcMfapPkGOH8DjPSAw;Database=saps"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
@@ -7,7 +10,7 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user