Controllers

This commit is contained in:
Karolis Kundrotas
2021-10-29 09:17:17 +03:00
parent aff6f8df82
commit ba413d4330
22 changed files with 675 additions and 292 deletions

View File

@@ -4,6 +4,22 @@
"lockfileVersion": 1,
"requires": true,
"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": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@@ -4289,7 +4305,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
@@ -7626,8 +7641,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "6.2.3",

View File

@@ -8,6 +8,7 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@azure/msal-browser": "^2.18.0",
"@popperjs/core": "^2.10.1",
"axios": "^0.20.0-0",
"bootstrap": "^5.1.1",

View File

@@ -19,15 +19,6 @@
</span>
</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 />
</template>
@@ -46,7 +37,7 @@ export default {
}
},
created() {
this.$store.dispatch('auth/initialize')
this.$store.dispatch('msalAuth/initialize')
},
computed: {
isLocal() {
@@ -58,26 +49,6 @@ export default {
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>

View File

@@ -27,18 +27,19 @@
>Pagrindinis</router-link
>
</li>
<li class="nav-item">
<!-- <li class="nav-item">
<a href="/swagger" class="nav-link">Swagger UI</a>
</li>
</li> -->
</ul>
<div class="navbar-nav">
<span v-if="$store.getters['auth/isValid']" class="navbar-text"
>Prisijungta kaip {{ $store.getters['auth/email'] }}</span
<span v-if="$store.state.msalAuth.isLoggedIn" class="navbar-text"
>Prisijungta kaip {{ $store.state.msalAuth.displayName }}
<a href="#" @click="LogoutMsal" class="nav-link"
>Atsijungti</a
></span
>
<div v-else class="nav-item">
<a :href="$store.getters['auth/loginUrl']" class="nav-link"
>Prisijungti</a
>
<a href="#" @click="LoginMsal" class="nav-link">Prisijungti</a>
</div>
</div>
</div>
@@ -48,6 +49,8 @@
</template>
<script>
import { LoginMsal, LogoutMsal } from '@/msal'
export default {
name: 'NavMenu',
data() {
@@ -56,6 +59,8 @@ export default {
}
},
methods: {
LoginMsal,
LogoutMsal,
collapse() {
this.isExpanded = false
},

View File

@@ -4,6 +4,7 @@ import router from './router'
import store from './store'
import './assets/main.scss'
import 'bootstrap'
import './msal'
const app = createApp(App)

View 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()

View File

@@ -2,39 +2,34 @@
<div class="container">
<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">
<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>
el. paštas yra: <b>{{ $store.state.msalAuth.email }}</b>
</span>
</div>
<h2>Visi laukai gaunami 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>
<h3>Priegos 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 apie laukų reikšmes</a
>Dokumentacija</a
>
<pre>{{ $store.state.auth.tokenData }}</pre>
<h3>Saugos raktas.</h3>
<pre>{{ $store.state.auth.token }}</pre>
<pre>{{ $store.state.msalAuth.idToken }}</pre>
<h4>Duomenys gaunami 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
@@ -54,46 +49,20 @@
<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('/api/AuthMetadata/authed', {
headers: { Authorization: `Bearer ${this.$store.state.auth.token}` },
headers: {
Authorization: `Bearer ${this.$store.state.msalAuth.idToken}`,
},
})
.then(response => {
this.verificationResult = response.data

View File

@@ -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>

View File

@@ -1,6 +1,5 @@
import { createWebHistory, createRouter } from 'vue-router'
import Home from '@/pages/Home.vue'
import OidcEndpoint from '@/pages/OidcEndpoint.vue'
const routes = [
{
@@ -8,11 +7,6 @@ const routes = [
name: 'Home',
component: Home,
},
{
path: '/oidc',
name: 'OpenID connect endpoint',
component: OidcEndpoint,
},
]
const router = createRouter({

View File

@@ -1,11 +1,11 @@
import { createStore, createLogger } from 'vuex'
import auth from './modules/auth'
import msalAuth from './modules/msalAuth'
const debug = process.env.NODE_ENV !== 'production'
export default createStore({
modules: {
auth,
msalAuth,
},
strict: debug,
plugins: debug ? [createLogger()] : [],

View File

@@ -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,
}

View 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,
}

View 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();
}
}
}

View File

@@ -11,11 +11,11 @@ namespace KTUSAPS.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class IssueController : ControllerBase
public class IssuesController : ControllerBase
{
private readonly Data.SAPSDataContext dataContext;
public IssueController(Data.SAPSDataContext dataContext)
public IssuesController(Data.SAPSDataContext dataContext)
{
this.dataContext = dataContext;
}
@@ -23,9 +23,9 @@ namespace KTUSAPS.Controllers
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public IEnumerable<Issue> GetIssues()
public async Task<ActionResult<IEnumerable<Issue>>> GetIssues()
{
return dataContext.Issues;
return await dataContext.Issues.ToListAsync();
}
[HttpPost]
@@ -47,8 +47,8 @@ namespace KTUSAPS.Controllers
var createdValue = await dataContext.AddAsync(issueToCreate);
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}")]
@@ -77,7 +77,7 @@ namespace KTUSAPS.Controllers
nameof(databaseIssue.Publishable)
});
await dataContext.SaveChangesAsync();
return Ok(eIssue.Entity);
return NoContent();
}
[HttpDelete("{id}")]

View 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();
}
}
}

View 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;
}
}
}

View File

@@ -12,6 +12,16 @@
<ItemGroup>
<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.Annotations" Version="6.2.1" />
<PackageReference Include="VueCliMiddleware" Version="5.0.0" />

View File

@@ -32,8 +32,8 @@ namespace KTUSAPS
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";
options.Audience = Configuration["ClientId"];
options.Authority = Configuration["Authority"];
});
services.AddAuthorization((configure) =>

View File

@@ -9,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"
}

View File

@@ -10,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"
}