Controllers
This commit is contained in:
@@ -12,7 +12,7 @@ namespace KTUSAPS.Data.Model
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string NameEn { get; set; }
|
public string NameEn { get; set; }
|
||||||
|
|
||||||
public virtual ICollection<Issue> Issues { get; set; }
|
public virtual HashSet<Issue> Issues { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ namespace KTUSAPS.Data.Model
|
|||||||
|
|
||||||
public int? SolutionId { get; set; }
|
public int? SolutionId { get; set; }
|
||||||
public virtual Solution Solution { get; set; }
|
public virtual Solution Solution { get; set; }
|
||||||
public ICollection<Vote> Votes { get; set; }
|
public virtual HashSet<Vote> Votes { get; set; }
|
||||||
|
|
||||||
public PublishedProblem()
|
public PublishedProblem()
|
||||||
{
|
{
|
||||||
|
20
KTUSAPS/ClientApp/package-lock.json
generated
20
KTUSAPS/ClientApp/package-lock.json
generated
@@ -4,6 +4,22 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/msal-browser": {
|
||||||
|
"version": "2.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.18.0.tgz",
|
||||||
|
"integrity": "sha512-YiWsimjsjjVu56+zOUDB1U3BCD9YNPIEZmw5iHzMk14aanqxWIvJlo+Ewo5/3FqxILgBOFWliH2hZCQPRIqKSg==",
|
||||||
|
"requires": {
|
||||||
|
"@azure/msal-common": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@azure/msal-common": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-CmPR3XM9+CGUu7V/+bAwDxyN6XqWJJhVLmv7utT3sbgay4l5roVXsD1t4wURTs8PwzxmmnJOrhvvGhoDxUW69g==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.10.4",
|
"version": "7.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||||
@@ -4289,7 +4305,6 @@
|
|||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
}
|
}
|
||||||
@@ -7626,8 +7641,7 @@
|
|||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"multicast-dns": {
|
"multicast-dns": {
|
||||||
"version": "6.2.3",
|
"version": "6.2.3",
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/msal-browser": "^2.18.0",
|
||||||
"@popperjs/core": "^2.10.1",
|
"@popperjs/core": "^2.10.1",
|
||||||
"axios": "^0.20.0-0",
|
"axios": "^0.20.0-0",
|
||||||
"bootstrap": "^5.1.1",
|
"bootstrap": "^5.1.1",
|
||||||
|
@@ -19,15 +19,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$store.getters['auth/isExpiringSoon']" class="container">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<h4 class="alert-heading">Greitai baisis jūsų sesija</h4>
|
|
||||||
<span>
|
|
||||||
Po {{ expiresIn }} baigsis jūsų sesija.
|
|
||||||
<a :href="$store.getters['auth/loginUrl']">Pratęsti sesija.</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -46,7 +37,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$store.dispatch('auth/initialize')
|
this.$store.dispatch('msalAuth/initialize')
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isLocal() {
|
isLocal() {
|
||||||
@@ -58,26 +49,6 @@ export default {
|
|||||||
return location.protocol !== 'https:'
|
return location.protocol !== 'https:'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
updateExpiry() {
|
|
||||||
const totalSeconds = Math.floor(
|
|
||||||
(this.$store.getters['auth/expires'] - new Date()) / 1000
|
|
||||||
)
|
|
||||||
const seconds = totalSeconds % 60
|
|
||||||
const minutes = Math.floor(totalSeconds / 60)
|
|
||||||
if (minutes) {
|
|
||||||
this.expiresIn = `${minutes} min. ir ${seconds} sek.`
|
|
||||||
} else {
|
|
||||||
this.expiresIn = `${seconds} sek.`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.interval = setInterval(this.updateExpiry, 1000)
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
clearInterval(this.interval)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@@ -27,18 +27,19 @@
|
|||||||
>Pagrindinis</router-link
|
>Pagrindinis</router-link
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<!-- <li class="nav-item">
|
||||||
<a href="/swagger" class="nav-link">Swagger UI</a>
|
<a href="/swagger" class="nav-link">Swagger UI</a>
|
||||||
</li>
|
</li> -->
|
||||||
</ul>
|
</ul>
|
||||||
<div class="navbar-nav">
|
<div class="navbar-nav">
|
||||||
<span v-if="$store.getters['auth/isValid']" class="navbar-text"
|
<span v-if="$store.state.msalAuth.isLoggedIn" class="navbar-text"
|
||||||
>Prisijungta kaip {{ $store.getters['auth/email'] }}</span
|
>Prisijungta kaip {{ $store.state.msalAuth.displayName }}
|
||||||
|
<a href="#" @click="LogoutMsal" class="nav-link"
|
||||||
|
>Atsijungti</a
|
||||||
|
></span
|
||||||
>
|
>
|
||||||
<div v-else class="nav-item">
|
<div v-else class="nav-item">
|
||||||
<a :href="$store.getters['auth/loginUrl']" class="nav-link"
|
<a href="#" @click="LoginMsal" class="nav-link">Prisijungti</a>
|
||||||
>Prisijungti</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,6 +49,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { LoginMsal, LogoutMsal } from '@/msal'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NavMenu',
|
name: 'NavMenu',
|
||||||
data() {
|
data() {
|
||||||
@@ -56,6 +59,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
LoginMsal,
|
||||||
|
LogoutMsal,
|
||||||
collapse() {
|
collapse() {
|
||||||
this.isExpanded = false
|
this.isExpanded = false
|
||||||
},
|
},
|
||||||
|
@@ -4,6 +4,7 @@ import router from './router'
|
|||||||
import store from './store'
|
import store from './store'
|
||||||
import './assets/main.scss'
|
import './assets/main.scss'
|
||||||
import 'bootstrap'
|
import 'bootstrap'
|
||||||
|
import './msal'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
183
KTUSAPS/ClientApp/src/msal.js
Normal file
183
KTUSAPS/ClientApp/src/msal.js
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import * as msal from '@azure/msal-browser'
|
||||||
|
import Cookies from 'cookies-js'
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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 __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())
|
||||||
|
}
|
||||||
|
|
||||||
|
function __isLocalStorageAvailable() {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('__lsTest', 'true')
|
||||||
|
const result = localStorage.getItem('__lsTest')
|
||||||
|
localStorage.removeItem('__lsTest')
|
||||||
|
return result == 'true'
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function __loadAuthParameters() {
|
||||||
|
if (__isLocalStorageAvailable()) {
|
||||||
|
await __loadAuthParametersLocalStorage()
|
||||||
|
} else {
|
||||||
|
await __loadAuthParametersCookies()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 __loadAuthParametersCookies() {
|
||||||
|
const clientId = Cookies.get(ClientIdCookieName)
|
||||||
|
const authority = Cookies.get(AuthorityCookieName)
|
||||||
|
const tenant = Cookies.get(TenantCookieName)
|
||||||
|
if (clientId == null || authority == null || tenant == null) {
|
||||||
|
await __fetchAuthParameters()
|
||||||
|
Cookies.set(ClientIdCookieName, msalState.clientId)
|
||||||
|
Cookies.set(AuthorityCookieName, msalState.authority)
|
||||||
|
Cookies.set(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()
|
@@ -2,39 +2,34 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>KTU SA Problemų sprendimo sistema</h1>
|
<h1>KTU SA Problemų sprendimo sistema</h1>
|
||||||
|
|
||||||
<template v-if="$store.getters['auth/isValid']">
|
<template v-if="$store.state.msalAuth.isLoggedIn">
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<h4 class="alert-heading">Tu esi prisijungęs</h4>
|
<h4 class="alert-heading">Tu esi prisijungęs</h4>
|
||||||
<span>
|
<span>
|
||||||
Kliento aplikacija turi tavo saugos raktą. Aplikacija žino, kad tavo
|
Kliento aplikacija turi tavo saugos raktą. Aplikacija žino, kad tavo
|
||||||
el. paštas yra: <b>{{ $store.getters['auth/email'] }}</b>
|
el. paštas yra: <b>{{ $store.state.msalAuth.email }}</b>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h2>Visi laukai gaunami iš Azure Active Directory</h2>
|
<h3>Priegos raktas (Access token)</h3>
|
||||||
<table class="table">
|
<a
|
||||||
<thead>
|
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens"
|
||||||
<tr>
|
>Dokumentacija</a
|
||||||
<th scope="col">Pavadinimas</th>
|
>
|
||||||
<th scope="col">Reikšmė</th>
|
<pre>{{ $store.state.msalAuth.accessToken }}</pre>
|
||||||
</tr>
|
|
||||||
</thead>
|
<h3>Indentifikacijos raktas (ID Token)</h3>
|
||||||
<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
|
<a
|
||||||
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens"
|
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens"
|
||||||
>Dokumentacija apie laukų reikšmes</a
|
>Dokumentacija</a
|
||||||
>
|
>
|
||||||
<pre>{{ $store.state.auth.tokenData }}</pre>
|
<pre>{{ $store.state.msalAuth.idToken }}</pre>
|
||||||
<h3>Saugos raktas.</h3>
|
|
||||||
<pre>{{ $store.state.auth.token }}</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>
|
<h3>Serverio tokeno patikrinimas</h3>
|
||||||
<button type="button" class="btn btn-primary" @click="serverVerify">
|
<button type="button" class="btn btn-primary" @click="serverVerify">
|
||||||
Patikrinti
|
Patikrinti
|
||||||
@@ -54,46 +49,20 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
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 {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
verificationResult: null,
|
verificationResult: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
authDataTable() {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(this.$store.state.auth.tokenData).map(([key, value]) => [
|
|
||||||
lookupName(key),
|
|
||||||
value,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
serverVerify() {
|
serverVerify() {
|
||||||
this.verificationResult = null
|
this.verificationResult = null
|
||||||
axios
|
axios
|
||||||
.get('/api/AuthMetadata/authed', {
|
.get('/api/AuthMetadata/authed', {
|
||||||
headers: { Authorization: `Bearer ${this.$store.state.auth.token}` },
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.$store.state.msalAuth.idToken}`,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.verificationResult = response.data
|
this.verificationResult = response.data
|
||||||
|
@@ -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,6 +1,5 @@
|
|||||||
import { createWebHistory, createRouter } from 'vue-router'
|
import { createWebHistory, createRouter } from 'vue-router'
|
||||||
import Home from '@/pages/Home.vue'
|
import Home from '@/pages/Home.vue'
|
||||||
import OidcEndpoint from '@/pages/OidcEndpoint.vue'
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -8,11 +7,6 @@ const routes = [
|
|||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: Home,
|
component: Home,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/oidc',
|
|
||||||
name: 'OpenID connect endpoint',
|
|
||||||
component: OidcEndpoint,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { createStore, createLogger } from 'vuex'
|
import { createStore, createLogger } from 'vuex'
|
||||||
import auth from './modules/auth'
|
import msalAuth from './modules/msalAuth'
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== 'production'
|
const debug = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
modules: {
|
modules: {
|
||||||
auth,
|
msalAuth,
|
||||||
},
|
},
|
||||||
strict: debug,
|
strict: debug,
|
||||||
plugins: debug ? [createLogger()] : [],
|
plugins: debug ? [createLogger()] : [],
|
||||||
|
@@ -1,153 +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.sub
|
|
||||||
},
|
|
||||||
isExpiringSoon(state, getters) {
|
|
||||||
if (!getters.isValid) return false
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
expires(state, getters) {
|
|
||||||
if (!getters.isValid) return 0
|
|
||||||
return new Date(state.tokenData.exp * 1000)
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
}
|
|
46
KTUSAPS/ClientApp/src/store/modules/msalAuth.js
Normal file
46
KTUSAPS/ClientApp/src/store/modules/msalAuth.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { WatchMsalState, GetMsalState } from '@/msal'
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
const state = () => ({
|
||||||
|
isLoggedIn: false,
|
||||||
|
accessToken: null,
|
||||||
|
idToken: null,
|
||||||
|
email: null,
|
||||||
|
displayName: null,
|
||||||
|
|
||||||
|
debugFullTokenResponse: null,
|
||||||
|
debugAccountInfo: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const getters = {}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
const actions = {
|
||||||
|
initialize({ commit }) {
|
||||||
|
WatchMsalState(() => {
|
||||||
|
commit('setState', GetMsalState())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutations
|
||||||
|
const mutations = {
|
||||||
|
setState(state, msalState) {
|
||||||
|
state.isLoggedIn = msalState.isLoggedIn
|
||||||
|
state.accessToken = msalState.accessToken
|
||||||
|
state.idToken = msalState.idToken
|
||||||
|
state.debugFullTokenResponse = msalState.debugFullTokenResponse
|
||||||
|
state.debugAccountInfo = msalState.debugAccountInfo
|
||||||
|
state.email = msalState.email
|
||||||
|
state.displayName = msalState.displayName
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
}
|
101
KTUSAPS/Controllers/IssueTypesController.cs
Normal file
101
KTUSAPS/Controllers/IssueTypesController.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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)]
|
||||||
|
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 issueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
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 NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -11,11 +11,11 @@ namespace KTUSAPS.Controllers
|
|||||||
{
|
{
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class IssueController : ControllerBase
|
public class IssuesController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly Data.SAPSDataContext dataContext;
|
private readonly Data.SAPSDataContext dataContext;
|
||||||
|
|
||||||
public IssueController(Data.SAPSDataContext dataContext)
|
public IssuesController(Data.SAPSDataContext dataContext)
|
||||||
{
|
{
|
||||||
this.dataContext = dataContext;
|
this.dataContext = dataContext;
|
||||||
}
|
}
|
||||||
@@ -23,9 +23,9 @@ namespace KTUSAPS.Controllers
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public IEnumerable<Issue> GetIssues()
|
public async Task<ActionResult<IEnumerable<Issue>>> GetIssues()
|
||||||
{
|
{
|
||||||
return dataContext.Issues;
|
return await dataContext.Issues.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -47,8 +47,8 @@ namespace KTUSAPS.Controllers
|
|||||||
|
|
||||||
var createdValue = await dataContext.AddAsync(issueToCreate);
|
var createdValue = await dataContext.AddAsync(issueToCreate);
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
var url = Url.ActionLink(action: nameof(GetIssue), values: new { Id = createdValue.Entity.Id });
|
|
||||||
return Created(url, createdValue.Entity);
|
return CreatedAtAction(nameof(GetIssue), new { Id = createdValue.Entity.Id }, createdValue.Entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
@@ -77,7 +77,7 @@ namespace KTUSAPS.Controllers
|
|||||||
nameof(databaseIssue.Publishable)
|
nameof(databaseIssue.Publishable)
|
||||||
});
|
});
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
return Ok(eIssue.Entity);
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
103
KTUSAPS/Controllers/PublishedFeedbacksController.cs
Normal file
103
KTUSAPS/Controllers/PublishedFeedbacksController.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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)]
|
||||||
|
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.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> 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 NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
KTUSAPS/Controllers/PublishedProblemsController.cs
Normal file
163
KTUSAPS/Controllers/PublishedProblemsController.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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)]
|
||||||
|
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 privide 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.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> 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 NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
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.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Vote>> Vote(int id, Vote vote)
|
||||||
|
{
|
||||||
|
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||||
|
if (publishedProblem == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// TODO: Get user id from auth claims
|
||||||
|
if (vote.UserId == default)
|
||||||
|
return BadRequest("Please provide user id");
|
||||||
|
|
||||||
|
vote.Problem = publishedProblem;
|
||||||
|
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -12,6 +12,16 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.10" />
|
<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.11" />
|
||||||
|
<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="6.2.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.2.1" />
|
||||||
<PackageReference Include="VueCliMiddleware" Version="5.0.0" />
|
<PackageReference Include="VueCliMiddleware" Version="5.0.0" />
|
||||||
|
@@ -32,8 +32,8 @@ namespace KTUSAPS
|
|||||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
options.Audience = "5931fda0-e9e0-4754-80c2-18bcb9d9561a";
|
options.Audience = Configuration["ClientId"];
|
||||||
options.Authority = "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0";
|
options.Authority = Configuration["Authority"];
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddAuthorization((configure) =>
|
services.AddAuthorization((configure) =>
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ClientId": "5931fda0-e9e0-4754-80c2-18bcb9d9561a",
|
"ClientId": "411a8715-3d63-4a03-9be8-9370d920e36f",
|
||||||
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
||||||
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ClientId": "5931fda0-e9e0-4754-80c2-18bcb9d9561a",
|
"ClientId": "411a8715-3d63-4a03-9be8-9370d920e36f",
|
||||||
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
|
||||||
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user