A lot
This commit is contained in:
5397
KTUSAPS/ClientApp/package-lock.json
generated
5397
KTUSAPS/ClientApp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,27 +8,29 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^2.18.0",
|
||||
"@popperjs/core": "^2.10.1",
|
||||
"@azure/msal-browser": "^2.20.0",
|
||||
"@popperjs/core": "^2.11.0",
|
||||
"axios": "^0.20.0-0",
|
||||
"bootstrap": "^5.1.1",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-icons": "^1.7.2",
|
||||
"cookies-js": "^1.2.3",
|
||||
"core-js": "^3.7.0",
|
||||
"core-js": "^3.20.0",
|
||||
"joi": "^17.5.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",
|
||||
"vue": "^3.2.26",
|
||||
"vue-loader-v16": "npm:vue-loader@^16.8.3",
|
||||
"vue-router": "^4.0.12",
|
||||
"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",
|
||||
"@vue/cli-plugin-babel": "^4.5.15",
|
||||
"@vue/cli-plugin-eslint": "^4.5.15",
|
||||
"@vue/cli-service": "^4.5.15",
|
||||
"@vue/compiler-sfc": "^3.2.26",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-vue": "^7.1.0",
|
||||
"sass": "^1.33.0",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"sass": "^1.45.1",
|
||||
"sass-loader": "^10.2.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<nav-menu></nav-menu>
|
||||
<nav-menu />
|
||||
<data-alert />
|
||||
<div v-if="isLocal" class="container">
|
||||
<div class="alert alert-danger">
|
||||
<h4 class="alert-heading">Lokali sistemą aptikta</h4>
|
||||
@@ -19,16 +20,20 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="slide-fade">
|
||||
<router-view />
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavMenu from './components/NavMenu.vue'
|
||||
import DataAlert from './components/DataAlert.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
NavMenu,
|
||||
DataAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@@ -1 +1,5 @@
|
||||
|
||||
$font-family-base: 'Open Sans', sans-serif;
|
||||
|
||||
$primary: #1862a1;
|
||||
$danger: #a1181d;
|
||||
$warning: #eb7d1d;
|
||||
|
@@ -1,2 +1,33 @@
|
||||
@import "custom";
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@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.3s ease-out;
|
||||
}
|
||||
|
||||
.slide-fade-leave-active {
|
||||
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
.slide-fade-leave-active,
|
||||
.slide-fade-enter-active {
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
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/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>
|
@@ -32,6 +32,21 @@
|
||||
>Pateikti problemą</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item" v-if="$store.state.msalAuth.isLoggedIn">
|
||||
<router-link :to="{ name: 'Problems' }" class="nav-link"
|
||||
>Problemos</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item" v-if="$store.state.msalAuth.isLoggedIn">
|
||||
<router-link :to="{ name: 'Feedbacks' }" class="nav-link"
|
||||
>Atsiliepimai</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="nav-item" v-if="$store.state.msalAuth.isAdmin">
|
||||
<router-link :to="{ name: 'Issues' }" class="nav-link"
|
||||
>Pateiktos problemos</router-link
|
||||
>
|
||||
</li>
|
||||
<!-- <li class="nav-item">
|
||||
<a href="/swagger" class="nav-link">Swagger UI</a>
|
||||
</li> -->
|
||||
|
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>
|
@@ -12,9 +12,7 @@
|
||||
</div>
|
||||
<template v-if="$store.state.msalAuth.isAdmin">
|
||||
<div class="alert alert-warning">
|
||||
<span>
|
||||
Tu esi administratorius.
|
||||
</span>
|
||||
<span> Tu esi administratorius. </span>
|
||||
</div>
|
||||
<h3>Prieigos raktas (Access token)</h3>
|
||||
<a
|
||||
@@ -59,7 +57,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { authAxios } from '@/axios'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -70,13 +68,9 @@ export default {
|
||||
methods: {
|
||||
serverVerify() {
|
||||
this.verificationResult = null
|
||||
axios
|
||||
.get('/api/AuthMetadata/authed', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.$store.state.msalAuth.idToken}`,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
authAxios
|
||||
.get('/api/AuthMetadata/authed')
|
||||
.then((response) => {
|
||||
this.verificationResult = response.data
|
||||
})
|
||||
.catch(function (error) {
|
||||
@@ -85,13 +79,9 @@ export default {
|
||||
},
|
||||
serverAdminVerify() {
|
||||
this.verificationResult = null
|
||||
axios
|
||||
.get('/api/AuthMetadata/Admin', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.$store.state.msalAuth.idToken}`,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
authAxios
|
||||
.get('/api/AuthMetadata/Admin')
|
||||
.then((response) => {
|
||||
this.verificationResult = response.data
|
||||
})
|
||||
.catch(function (error) {
|
||||
|
78
KTUSAPS/ClientApp/src/pages/Issues.vue
Normal file
78
KTUSAPS/ClientApp/src/pages/Issues.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<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 authAxios.delete(`/api/Issues/${id}`)
|
||||
this.ok = 'Sėkmingai ištrintą'
|
||||
await this.fetchData()
|
||||
} catch (error) {
|
||||
this.error = error
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
102
KTUSAPS/ClientApp/src/pages/NewFeedback.vue
Normal file
102
KTUSAPS/ClientApp/src/pages/NewFeedback.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>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">
|
||||
<div class="mb-3">
|
||||
<label for="problemLtTextArea" class="form-label"
|
||||
>Lietuviškas Aprašymas</label
|
||||
>
|
||||
<textarea
|
||||
v-model="feedback.feedbackLt"
|
||||
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="feedback.feedbackEn"
|
||||
class="form-control"
|
||||
id="problemEnTextArea"
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
<button @click="create" class="btn btn-primary btn-lg">
|
||||
Sukurti naują atsiliepimą
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { axios, authAxios } from '@/axios'
|
||||
|
||||
export default {
|
||||
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>
|
@@ -1,5 +1,7 @@
|
||||
<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>
|
||||
@@ -23,9 +25,9 @@
|
||||
id="issueTypeSelect"
|
||||
class="form-select"
|
||||
>
|
||||
<option v-for="it in issueTypes" :key="it.id" :value="it.id">{{
|
||||
it.name
|
||||
}}</option>
|
||||
<option v-for="it in issueTypes" :key="it.id" :value="it.id">
|
||||
{{ it.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@@ -53,15 +55,15 @@
|
||||
studentai.
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-primary">Pateikti</button>
|
||||
<button @click="submit" class="btn btn-primary btn-lg">Pateikti</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre>{{ $data }}</pre>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { authAxios, axios } from '@/axios'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -71,6 +73,8 @@ export default {
|
||||
issueTypeId: null,
|
||||
publishable: false,
|
||||
},
|
||||
error: null,
|
||||
ok: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -81,6 +85,20 @@ export default {
|
||||
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>
|
||||
|
@@ -1,6 +1,11 @@
|
||||
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'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -13,6 +18,31 @@ const routes = [
|
||||
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,
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
@@ -3,6 +3,7 @@ import axios from 'axios'
|
||||
|
||||
// initial state
|
||||
const state = () => ({
|
||||
isReady: false,
|
||||
isLoggedIn: false,
|
||||
isAdmin: false,
|
||||
accessToken: null,
|
||||
@@ -11,7 +12,8 @@ const state = () => ({
|
||||
displayName: null,
|
||||
|
||||
debugFullTokenResponse: null,
|
||||
debugAccountInfo: null,
|
||||
// debugAccountInfo: null,
|
||||
onReady: [],
|
||||
})
|
||||
|
||||
// getters
|
||||
@@ -30,12 +32,19 @@ const actions = {
|
||||
Authorization: `Bearer ${state.idToken}`,
|
||||
},
|
||||
})
|
||||
.then(res => (isAdmin = res.data))
|
||||
.catch(error => console.error(error))
|
||||
.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
|
||||
@@ -46,9 +55,16 @@ const mutations = {
|
||||
state.accessToken = msalState.accessToken
|
||||
state.idToken = msalState.idToken
|
||||
state.debugFullTokenResponse = msalState.debugFullTokenResponse
|
||||
state.debugAccountInfo = msalState.debugAccountInfo
|
||||
// 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)
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -45,6 +45,11 @@ namespace KTUSAPS.Controllers
|
||||
[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")]
|
||||
|
@@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using KTUSAPS.Data;
|
||||
using KTUSAPS.Data.Model;
|
||||
using KTUSAPS.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace KTUSAPS.Controllers
|
||||
{
|
||||
@@ -32,6 +33,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Authorize("admin")]
|
||||
public async Task<ActionResult<IssueType>> CreateIssueType([FromBody] IssueType issueType)
|
||||
{
|
||||
if (issueType == null)
|
||||
@@ -64,6 +66,7 @@ namespace KTUSAPS.Controllers
|
||||
[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);
|
||||
@@ -86,6 +89,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize("admin")]
|
||||
public async Task<IActionResult> DeleteIssueType(int id)
|
||||
{
|
||||
var issueType = await _context.IssueTypes.FindAsync(id);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using KTUSAPS.Data.Model;
|
||||
using KTUSAPS.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -23,6 +24,7 @@ namespace KTUSAPS.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[Authorize("admin")]
|
||||
public async Task<ActionResult<IEnumerable<Issue>>> GetIssues()
|
||||
{
|
||||
return await dataContext.Issues.ToListAsync();
|
||||
@@ -31,6 +33,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<Issue>> CreateIssueAsync([FromBody] Issue issueToCreate)
|
||||
{
|
||||
if (issueToCreate == null)
|
||||
@@ -41,9 +44,11 @@ namespace KTUSAPS.Controllers
|
||||
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.");
|
||||
// TODO: Enable next line and make thoes two fields come from user identity
|
||||
//if (issueToCreate.UserID != default || issueToCreate.Email != default)
|
||||
// return BadRequest("Do not provide indentity 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();
|
||||
@@ -54,6 +59,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpGet("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize("admin")]
|
||||
public ActionResult<Issue> GetIssue(int id)
|
||||
{
|
||||
var issue = dataContext.Issues.AsQueryable().Where(i => i.Id == id).FirstOrDefault();
|
||||
@@ -65,6 +71,7 @@ namespace KTUSAPS.Controllers
|
||||
[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();
|
||||
@@ -83,6 +90,7 @@ namespace KTUSAPS.Controllers
|
||||
[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();
|
||||
|
@@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using KTUSAPS.Data;
|
||||
using KTUSAPS.Data.Model;
|
||||
using KTUSAPS.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace KTUSAPS.Controllers
|
||||
{
|
||||
@@ -32,6 +33,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Authorize("admin")]
|
||||
public async Task<ActionResult<PublishedFeedback>> PostPublishedFeedback(PublishedFeedback publishedFeedback)
|
||||
{
|
||||
if (publishedFeedback == null)
|
||||
@@ -63,6 +65,7 @@ namespace KTUSAPS.Controllers
|
||||
[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);
|
||||
@@ -88,6 +91,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize("admin")]
|
||||
public async Task<IActionResult> DeletePublishedFeedback(int id)
|
||||
{
|
||||
var publishedFeedback = await _context.PublishedFeedbacks.FindAsync(id);
|
||||
|
@@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using KTUSAPS.Data;
|
||||
using KTUSAPS.Data.Model;
|
||||
using KTUSAPS.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace KTUSAPS.Controllers
|
||||
{
|
||||
@@ -32,6 +33,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Authorize("admin")]
|
||||
public async Task<ActionResult<PublishedProblem>> CreatePublishedProblem([FromBody] PublishedProblem publishedProblem)
|
||||
{
|
||||
if (publishedProblem == null)
|
||||
@@ -63,6 +65,7 @@ namespace KTUSAPS.Controllers
|
||||
[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);
|
||||
@@ -90,6 +93,7 @@ namespace KTUSAPS.Controllers
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Authorize("admin")]
|
||||
public async Task<IActionResult> DeletePublishedProblem(int id)
|
||||
{
|
||||
var publishedProblem = await _context.PublishedProblems.FindAsync(id);
|
||||
@@ -121,17 +125,18 @@ namespace KTUSAPS.Controllers
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Vote>> Vote(int id, Vote vote)
|
||||
[Authorize]
|
||||
public async Task<ActionResult<Vote>> Vote(int id)
|
||||
{
|
||||
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;
|
||||
var vote = new Vote()
|
||||
{
|
||||
Problem = publishedProblem,
|
||||
UserId = User.GetUserId(),
|
||||
};
|
||||
|
||||
_context.Votes.Add(vote);
|
||||
await _context.SaveChangesAsync();
|
||||
|
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");
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user