Merge branch 'main' of git.avatic.de:avatic/lcc_tool
This commit is contained in:
commit
562764561e
23 changed files with 664 additions and 65 deletions
|
|
@ -1,54 +1,50 @@
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
container_name: lcc-mysql
|
container_name: lcc-mysql-local
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||||
MYSQL_DATABASE: ${DB_DATABASE}
|
MYSQL_DATABASE: lcc
|
||||||
MYSQL_USER: ${DB_USER}
|
MYSQL_USER: ${SPRING_DATASOURCE_USERNAME}
|
||||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
MYSQL_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- mysql-data:/var/lib/mysql
|
- mysql-data-local:/var/lib/mysql
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
networks:
|
networks:
|
||||||
- lcc-network
|
- lcc-network-local
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
lcc-app:
|
lcc-app:
|
||||||
#image: git.avatic.de/avatic/lcc:latest
|
#image: git.avatic.de/avatic/lcc:latest
|
||||||
|
# Oder für lokales Bauen:
|
||||||
build: .
|
build: .
|
||||||
container_name: lcc-app
|
container_name: lcc-app-local
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
mysql:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
environment:
|
environment:
|
||||||
DB_DATABASE: ${DB_DATABASE}
|
# Überschreibe die Datasource URL für Docker-Netzwerk
|
||||||
DB_USER: ${DB_USER}
|
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/lcc
|
||||||
DB_PASSWORD: ${DB_PASSWORD}
|
|
||||||
ALLOWED_CORS_DOMAIN: ${ALLOWED_CORS_DOMAIN}
|
|
||||||
LCC_BASE_URL: ${LCC_BASE_URL}
|
|
||||||
AZURE_MAPS_CLIENT_ID: ${AZURE_MAPS_CLIENT_ID}
|
|
||||||
AZURE_MAPS_SUBSCRIPTION_KEY: ${AZURE_MAPS_SUBSCRIPTION_KEY}
|
|
||||||
AZURE_TENANT_ID: ${AZURE_TENANT_ID}
|
|
||||||
AZURE_CLIENT_ID: ${AZURE_CLIENT_ID}
|
|
||||||
AZURE_CLIENT_SECRET: ${AZURE_CLIENT_SECRET}
|
|
||||||
JWT_SECRET: ${JWT_SECRET}
|
|
||||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${DB_DATABASE}
|
|
||||||
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
|
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
networks:
|
networks:
|
||||||
- lcc-network
|
- lcc-network-local
|
||||||
|
dns:
|
||||||
|
- 8.8.8.8
|
||||||
|
- 8.8.4.4
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql-data:
|
mysql-data-local:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
lcc-network:
|
lcc-network-local:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
226
src/frontend/src/components/UI/AddApp.vue
Normal file
226
src/frontend/src/components/UI/AddApp.vue
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
<template>
|
||||||
|
<div class="stage-container">
|
||||||
|
<transition name="slide-fade" mode="out-in">
|
||||||
|
<div v-if="stage1" key="stage1">
|
||||||
|
<div class="add-app">
|
||||||
|
<div>App name</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container"><input class="input-field" v-model="appName" @input="checkChange"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="add-app-group-header">App groups</div>
|
||||||
|
<div>
|
||||||
|
<app-group-item v-for="group in groups" :group-obj="group" :key="group.group_name"
|
||||||
|
:selected="isSelected(group.group_name)"
|
||||||
|
@checkbox-changed="updateSelected"></app-group-item>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="add-app-footer">
|
||||||
|
<basic-button :show-icon="false" @click="addApp" :disabled="disableButton">Add app</basic-button>
|
||||||
|
<basic-button :show-icon="false" @click="closeModal(false)" variant="secondary">Cancel</basic-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else key="stage2">
|
||||||
|
<div class="secret-warning">
|
||||||
|
<ph-warning size="18px"></ph-warning>
|
||||||
|
This is your app's client secret. Please keep it safe. It will not be shown again.
|
||||||
|
</div>
|
||||||
|
<box variant="border" class="add-app-secret">
|
||||||
|
<div class="secret-header">App</div>
|
||||||
|
<div class="secret">{{ appName }}</div>
|
||||||
|
<div class="secret-header">Client Id</div>
|
||||||
|
<div class="secret">{{ clientId }}</div>
|
||||||
|
<div class="secret-header">Client secret</div>
|
||||||
|
<div class="secret">{{ clientSecret }}</div>
|
||||||
|
</box>
|
||||||
|
<div class="add-app-footer">
|
||||||
|
<basic-button :show-icon="false" @click="closeModal(true)">Close</basic-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import InputField from "@/components/UI/InputField.vue";
|
||||||
|
import {mapStores} from "pinia";
|
||||||
|
import {useGroupStore} from "@/store/group.js";
|
||||||
|
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
|
import AppGroupItem from "@/components/UI/AppGroupItem.vue";
|
||||||
|
import {useAppsStore} from "@/store/apps.js";
|
||||||
|
import checkbox from "@/components/UI/Checkbox.vue";
|
||||||
|
import Box from "@/components/UI/Box.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddApp",
|
||||||
|
components: {Box, AppGroupItem, BasicButton, Checkbox, InputField},
|
||||||
|
computed: {
|
||||||
|
checkbox() {
|
||||||
|
return checkbox
|
||||||
|
},
|
||||||
|
...mapStores(useGroupStore, useAppsStore),
|
||||||
|
groups() {
|
||||||
|
return this.groupStore.groups;
|
||||||
|
},
|
||||||
|
disableButton() {
|
||||||
|
return this.appName === null || this.appName.length === 0 || Object.values(this.selectedGroups).every(value => !value);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.groupStore.loadGroups()
|
||||||
|
this.groupStore.groups?.forEach(group => {
|
||||||
|
this.selectedGroups[group.group_name] = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stage1: true,
|
||||||
|
appName: '',
|
||||||
|
selectedGroups: {},
|
||||||
|
clientSecret: '',
|
||||||
|
clientId: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isSelected(groupName) {
|
||||||
|
if (groupName === null || this.selectedGroups == null || Object.keys(this.selectedGroups).length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const group = this.selectedGroups[groupName];
|
||||||
|
if ((group ?? null) === null)
|
||||||
|
return false;
|
||||||
|
return group;
|
||||||
|
},
|
||||||
|
async addApp() {
|
||||||
|
if (!this.disableButton) {
|
||||||
|
const app = await this.appsStore.addApp(this.appName, this.selectedGroups);
|
||||||
|
this.stage1 = false;
|
||||||
|
console.log("app in add app", app);
|
||||||
|
this.clientSecret = app.client_secret;
|
||||||
|
this.clientId = app.client_id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateSelected(checked, groupName) {
|
||||||
|
this.selectedGroups[groupName] = checked;
|
||||||
|
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.stage-container {
|
||||||
|
position: relative;
|
||||||
|
min-height: 20rem;
|
||||||
|
margin: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transition animations */
|
||||||
|
.slide-fade-enter-active {
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-from {
|
||||||
|
transform: translateX(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
transform: translateX(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
grid-gap: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-app-group-header {
|
||||||
|
align-self: start;
|
||||||
|
padding-top: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
/* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);*/
|
||||||
|
border: 0.2rem solid #E3EDFF;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #002F54;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-app-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secret-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
gap: 1.6rem;
|
||||||
|
background-color: #c3cfdf;
|
||||||
|
color: #002F54;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
padding: 1.6rem;
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secret-header {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #002F54;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secret {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-app-secret {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-gap: 2.4rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
60
src/frontend/src/components/UI/AppGroupItem.vue
Normal file
60
src/frontend/src/components/UI/AppGroupItem.vue
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-group-item">
|
||||||
|
<checkbox :checked="selected" @checkbox-changed="checkboxChanged"></checkbox>
|
||||||
|
<div>
|
||||||
|
<div class="app-group-item-name">{{ groupObj.group_name }}</div>
|
||||||
|
<div class="app-group-item-descr">{{ groupObj.group_description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AppGroupItem",
|
||||||
|
components: {Checkbox},
|
||||||
|
props: {
|
||||||
|
groupObj: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkboxChanged(checked) {
|
||||||
|
this.$emit('checkbox-changed', checked, this.groupObj.group_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.app-group-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 0.8rem;
|
||||||
|
padding: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-group-item-name {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-group-item-descr {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
83
src/frontend/src/components/UI/AppListItem.vue
Normal file
83
src/frontend/src/components/UI/AppListItem.vue
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="app-list-item">
|
||||||
|
<div class="app-name-container"><div class="app-name-name">{{ app.name }}</div><div class="app-name-id">{{ app.client_id}}</div></div>
|
||||||
|
|
||||||
|
<div class="badge-list"> <basic-badge variant="secondary" icon="lock" v-for="group in groups" :key="group">{{group}}</basic-badge></div>
|
||||||
|
<div class="action-container"> <icon-button icon="trash" @click="deleteClick"></icon-button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Box from "@/components/UI/Box.vue";
|
||||||
|
import IconButton from "@/components/UI/IconButton.vue";
|
||||||
|
import BasicBadge from "@/components/UI/BasicBadge.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AppListItem",
|
||||||
|
components: {BasicBadge, IconButton, Box},
|
||||||
|
emits: ["deleteApp"],
|
||||||
|
props: {
|
||||||
|
app: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
groups() {
|
||||||
|
return this.app.groups;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteClick() {
|
||||||
|
this.$emit("deleteApp", this.app.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-list-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 0.5fr;
|
||||||
|
grid-gap: 2.4rem;
|
||||||
|
padding: 1.6rem 0;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-bottom: 0.1rem solid #E3EDFF;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.app-name-name {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name-id {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-container{
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
86
src/frontend/src/components/layout/config/Apps.vue
Normal file
86
src/frontend/src/components/layout/config/Apps.vue
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="app-list-header">
|
||||||
|
<div>App</div>
|
||||||
|
<div>Groups</div>
|
||||||
|
<div>Action</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-list">
|
||||||
|
|
||||||
|
<app-list-item v-for="app in apps" :app="app" @delete-app="deleteApp"></app-list-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<modal :state="modalState">
|
||||||
|
<add-app @close="closeModal"></add-app>
|
||||||
|
</modal>
|
||||||
|
<basic-button icon="Plus" @click="modalState = true">New App</basic-button>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
|
import AppListItem from "@/components/UI/AppListItem.vue";
|
||||||
|
import {mapStores} from "pinia";
|
||||||
|
import {useAppsStore} from "@/store/apps.js";
|
||||||
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
|
import AddApp from "@/components/UI/AddApp.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Apps",
|
||||||
|
props: {
|
||||||
|
isSelected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {AddApp, Modal, AppListItem, BasicButton},
|
||||||
|
computed: {
|
||||||
|
...mapStores(useAppsStore),
|
||||||
|
apps() {
|
||||||
|
return this.appsStore.apps;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modalState: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async closeModal(success) {
|
||||||
|
this.modalState = false;
|
||||||
|
if (success) {
|
||||||
|
await this.appsStore.loadApps();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteApp(id) {
|
||||||
|
this.appsStore.deleteApp(id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.appsStore.loadApps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.app-list-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr 0.5fr;
|
||||||
|
grid-gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #6B869C;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08rem;
|
||||||
|
border-bottom: 0.1rem solid #E3EDFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-list {
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -27,6 +27,7 @@ import Materials from "@/components/layout/config/Materials.vue";
|
||||||
import ErrorLog from "@/pages/ErrorLog.vue";
|
import ErrorLog from "@/pages/ErrorLog.vue";
|
||||||
import {mapStores} from "pinia";
|
import {mapStores} from "pinia";
|
||||||
import {useActiveUserStore} from "@/store/activeuser.js";
|
import {useActiveUserStore} from "@/store/activeuser.js";
|
||||||
|
import Apps from "@/components/layout/config/Apps.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Config",
|
name: "Config",
|
||||||
|
|
@ -39,6 +40,11 @@ export default {
|
||||||
component: markRaw(Properties),
|
component: markRaw(Properties),
|
||||||
props: {isSelected: false},
|
props: {isSelected: false},
|
||||||
},
|
},
|
||||||
|
appsTab: {
|
||||||
|
title: 'Apps',
|
||||||
|
component: markRaw(Apps),
|
||||||
|
props: {isSelected: false},
|
||||||
|
},
|
||||||
systemLogTab: {
|
systemLogTab: {
|
||||||
title: 'System log',
|
title: 'System log',
|
||||||
component: markRaw(ErrorLog),
|
component: markRaw(ErrorLog),
|
||||||
|
|
@ -76,6 +82,10 @@ export default {
|
||||||
tabs.push(this.systemLogTab);
|
tabs.push(this.systemLogTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.activeUserStore.isService) {
|
||||||
|
tabs.push(this.appsTab);
|
||||||
|
}
|
||||||
|
|
||||||
tabs.push(this.materialsTab);
|
tabs.push(this.materialsTab);
|
||||||
tabs.push(this.nodesTab);
|
tabs.push(this.nodesTab);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const useActiveUserStore = defineStore('activeUser', {
|
||||||
allowConfiguration(state) {
|
allowConfiguration(state) {
|
||||||
if (state.user === null)
|
if (state.user === null)
|
||||||
return false;
|
return false;
|
||||||
return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging");
|
return state.user.groups?.includes("super") || state.user.groups?.includes("freight") || state.user.groups?.includes("packaging") || state.user.groups?.includes("service");
|
||||||
},
|
},
|
||||||
allowReporting(state) {
|
allowReporting(state) {
|
||||||
if (state.user === null)
|
if (state.user === null)
|
||||||
|
|
@ -30,6 +30,11 @@ export const useActiveUserStore = defineStore('activeUser', {
|
||||||
return false;
|
return false;
|
||||||
return state.user.groups?.includes("super");
|
return state.user.groups?.includes("super");
|
||||||
},
|
},
|
||||||
|
isService(state) {
|
||||||
|
if (state.user === null)
|
||||||
|
return false;
|
||||||
|
return state.user.groups?.includes("service");
|
||||||
|
},
|
||||||
isPackaging(state) {
|
isPackaging(state) {
|
||||||
if (state.user === null)
|
if (state.user === null)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
72
src/frontend/src/store/apps.js
Normal file
72
src/frontend/src/store/apps.js
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {config} from '@/config'
|
||||||
|
import {useErrorStore} from "@/store/error.js";
|
||||||
|
import performRequest from "@/backend.js";
|
||||||
|
import logger from "@/logger.js";
|
||||||
|
|
||||||
|
|
||||||
|
export const useAppsStore = defineStore('apps', {
|
||||||
|
state: () => ({
|
||||||
|
apps: [],
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getById: (state) => {
|
||||||
|
return (id) => state.apps.find(p => p.id === id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async loadApps() {
|
||||||
|
this.loading = true;
|
||||||
|
const url = `${config.backendUrl}/apps`;
|
||||||
|
const resp = await performRequest(this, 'GET', url, null);
|
||||||
|
this.apps = resp.data;
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
async addApp(appName, appGroups) {
|
||||||
|
const url = `${config.backendUrl}/apps`;
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
name: appName,
|
||||||
|
groups: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(appGroups)) {
|
||||||
|
if (value) {
|
||||||
|
app.groups.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const resp = await performRequest(this, 'POST', url, app);
|
||||||
|
this.apps.push(resp.data);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
async updateApp(appId, appName, appGroups) {
|
||||||
|
const url = `${config.backendUrl}/apps/${appId}`;
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
id: appId,
|
||||||
|
name: appName,
|
||||||
|
groups: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(appGroups)) {
|
||||||
|
if (value) {
|
||||||
|
app.groups.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await performRequest(this, 'POST', url, app);
|
||||||
|
const index = this.apps.findIndex(a => a.id === app.id);
|
||||||
|
this.apps[index] = resp.data;
|
||||||
|
},
|
||||||
|
async deleteApp(appId) {
|
||||||
|
const url = `${config.backendUrl}/apps/${appId}`;
|
||||||
|
await performRequest(this, 'DELETE', url, null, false);
|
||||||
|
this.apps = this.apps.filter(a => a.id !== appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
25
src/frontend/src/store/group.js
Normal file
25
src/frontend/src/store/group.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {defineStore} from 'pinia'
|
||||||
|
import {config} from '@/config'
|
||||||
|
import performRequest from "@/backend.js";
|
||||||
|
|
||||||
|
|
||||||
|
export const useGroupStore = defineStore('group', {
|
||||||
|
state: () => ({
|
||||||
|
groups: [],
|
||||||
|
loading: false,
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getById: (state) => {
|
||||||
|
return (id) => state.group.find(p => p.id === id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async loadGroups() {
|
||||||
|
this.loading = true;
|
||||||
|
const url = `${config.backendUrl}/groups`;
|
||||||
|
const resp = await performRequest(this,'GET', url, null);
|
||||||
|
this.groups = resp.data;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -20,12 +20,15 @@ import java.util.Arrays;
|
||||||
@Profile("dev | test")
|
@Profile("dev | test")
|
||||||
public class CorsConfig implements WebMvcConfigurer {
|
public class CorsConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired
|
private final Environment environment;
|
||||||
private Environment environment;
|
|
||||||
|
|
||||||
@Value("${lcc.allowed_cors}")
|
@Value("${lcc.allowed_cors}")
|
||||||
private String allowedCors;
|
private String allowedCors;
|
||||||
|
|
||||||
|
public CorsConfig(Environment environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCorsMappings(@NotNull CorsRegistry registry) {
|
public void addCorsMappings(@NotNull CorsRegistry registry) {
|
||||||
String[] activeProfiles = environment.getActiveProfiles();
|
String[] activeProfiles = environment.getActiveProfiles();
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ public class LccOidcUser extends DefaultOidcUser {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static User createDatabaseUser(String email, String firstName, String lastName, String workdayId) {
|
public static User createDatabaseUser(String email, String firstName, String lastName, String workdayId, boolean isFirstUser) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
|
|
||||||
Group group = new Group();
|
Group group = new Group();
|
||||||
group.setName("none");
|
group.setName(isFirstUser ? "service" : "none");
|
||||||
|
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
user.setFirstName(firstName == null ? "" : firstName);
|
user.setFirstName(firstName == null ? "" : firstName);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package de.avatic.lcc.config;
|
||||||
|
|
||||||
import de.avatic.lcc.model.db.users.User;
|
import de.avatic.lcc.model.db.users.User;
|
||||||
import de.avatic.lcc.repositories.users.GroupRepository;
|
import de.avatic.lcc.repositories.users.GroupRepository;
|
||||||
import de.avatic.lcc.repositories.users.JwtTokenService;
|
import de.avatic.lcc.service.apps.JwtTokenService;
|
||||||
import de.avatic.lcc.repositories.users.UserRepository;
|
import de.avatic.lcc.repositories.users.UserRepository;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
|
@ -48,9 +48,6 @@ import java.util.function.Supplier;
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Value("${lcc.base.url}")
|
|
||||||
private String baseUrl;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Profile("!dev & !test") // Only active when NOT in dev profile
|
@Profile("!dev & !test") // Only active when NOT in dev profile
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenService jwtTokenService) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtTokenService jwtTokenService) throws Exception {
|
||||||
|
|
@ -63,10 +60,6 @@ public class SecurityConfig {
|
||||||
)
|
)
|
||||||
.oauth2Login(oauth2 -> oauth2
|
.oauth2Login(oauth2 -> oauth2
|
||||||
.defaultSuccessUrl("/", true)
|
.defaultSuccessUrl("/", true)
|
||||||
// Redirect-URI explizit setzen:
|
|
||||||
// .redirectionEndpoint(redirection -> redirection
|
|
||||||
// .baseUri(baseUrl + "/login/oauth2/code/*")
|
|
||||||
// )
|
|
||||||
)
|
)
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
.jwt(jwt -> jwt
|
.jwt(jwt -> jwt
|
||||||
|
|
@ -178,8 +171,9 @@ public class SecurityConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
userRepository.update(LccOidcUser.createDatabaseUser(email, oidcUser.getGivenName(), oidcUser.getFamilyName(), workdayId));
|
var isFirstUser = userRepository.count() == 0;
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_NONE"));
|
userRepository.update(LccOidcUser.createDatabaseUser(email, oidcUser.getGivenName(), oidcUser.getFamilyName(), workdayId, isFirstUser));
|
||||||
|
mappedAuthorities.add(new SimpleGrantedAuthority(isFirstUser ? "ROLE_SERVICE" : "ROLE_NONE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
package de.avatic.lcc.config;
|
package de.avatic.lcc.config;
|
||||||
import de.avatic.lcc.repositories.users.JwtTokenService;
|
import de.avatic.lcc.service.apps.JwtTokenService;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
package de.avatic.lcc.dto.configuration.apps;
|
package de.avatic.lcc.controller.configuration;
|
||||||
|
|
||||||
import de.avatic.lcc.dto.users.AppDTO;
|
import com.azure.core.annotation.BodyParam;
|
||||||
import de.avatic.lcc.repositories.users.AppRepository;
|
import de.avatic.lcc.dto.configuration.apps.AppDTO;
|
||||||
import de.avatic.lcc.service.apps.AppsService;
|
import de.avatic.lcc.service.apps.AppsService;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -29,9 +26,15 @@ public class AppsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping({"", "/"})
|
@PostMapping({"", "/"})
|
||||||
public ResponseEntity<AppDTO> updateApp(AppDTO dto) {
|
public ResponseEntity<AppDTO> updateApp(@RequestBody AppDTO dto) {
|
||||||
return ResponseEntity.ok(appsService.updateApp(dto));
|
return ResponseEntity.ok(appsService.updateApp(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping({"/{id}", "/{id}/"})
|
||||||
|
public ResponseEntity<Void> deleteApp(@PathVariable Integer id) {
|
||||||
|
appsService.deleteApp(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package de.avatic.lcc.controller.token;
|
package de.avatic.lcc.controller.token;
|
||||||
|
|
||||||
import de.avatic.lcc.model.db.users.App;
|
import de.avatic.lcc.model.db.users.App;
|
||||||
import de.avatic.lcc.repositories.users.JwtTokenService;
|
import de.avatic.lcc.service.apps.JwtTokenService;
|
||||||
import de.avatic.lcc.service.apps.AppsService;
|
import de.avatic.lcc.service.apps.AppsService;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public class GroupController {
|
||||||
* @return A ResponseEntity containing the list of groups and pagination headers.
|
* @return A ResponseEntity containing the list of groups and pagination headers.
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/", ""})
|
@GetMapping({"/", ""})
|
||||||
@PreAuthorize("hasRole('RIGHT-MANAGMENT')")
|
@PreAuthorize("hasAnyRole('RIGHT-MANAGMENT', 'SERVICE')")
|
||||||
public ResponseEntity<List<GroupDTO>> listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit,
|
public ResponseEntity<List<GroupDTO>> listGroups(@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||||
@RequestParam(defaultValue = "1") @Min(1) int page) {
|
@RequestParam(defaultValue = "1") @Min(1) int page) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package de.avatic.lcc.dto.users;
|
package de.avatic.lcc.dto.configuration.apps;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
|
@ -86,11 +86,14 @@ public class AppRepository {
|
||||||
List<Integer> groupIds = groupRepository.findGroupIds(app.getGroups().stream().map(Group::getName).toList());
|
List<Integer> groupIds = groupRepository.findGroupIds(app.getGroups().stream().map(Group::getName).toList());
|
||||||
|
|
||||||
if (appId == null) {
|
if (appId == null) {
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO sys_app (name, client_id, client_secret)
|
||||||
|
VALUES (?, ?, ?)""";
|
||||||
|
|
||||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||||
jdbcTemplate.update(connection -> {
|
jdbcTemplate.update(connection -> {
|
||||||
PreparedStatement ps = connection.prepareStatement(
|
PreparedStatement ps = connection.prepareStatement(
|
||||||
"INSERT INTO sys_app (name, client_id, client_secret) " +
|
sql,
|
||||||
"VALUES (?, ?, ?)",
|
|
||||||
Statement.RETURN_GENERATED_KEYS
|
Statement.RETURN_GENERATED_KEYS
|
||||||
);
|
);
|
||||||
ps.setString(1, app.getName());
|
ps.setString(1, app.getName());
|
||||||
|
|
@ -104,7 +107,7 @@ public class AppRepository {
|
||||||
String query = """
|
String query = """
|
||||||
UPDATE sys_app SET name = ? WHERE id = ?""";
|
UPDATE sys_app SET name = ? WHERE id = ?""";
|
||||||
|
|
||||||
jdbcTemplate.update(query, app.getName());
|
jdbcTemplate.update(query, app.getName(), appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAppGroupMappings(appId, groupIds);
|
updateAppGroupMappings(appId, groupIds);
|
||||||
|
|
@ -150,13 +153,19 @@ public class AppRepository {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an app by id.
|
* Deletes an app by id.
|
||||||
|
* Also removes all associated group mappings.
|
||||||
*
|
*
|
||||||
* @param id id of the app to delete
|
* @param id id of the app to delete
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public void delete(Integer id) {
|
public void delete(Integer id) {
|
||||||
String sql = "DELETE FROM sys_app WHERE id = ?";
|
// First delete all group mappings for this app
|
||||||
jdbcTemplate.update(sql, id);
|
String deleteMappingsSql = "DELETE FROM sys_app_group_mapping WHERE app_id = ?";
|
||||||
|
jdbcTemplate.update(deleteMappingsSql, id);
|
||||||
|
|
||||||
|
// Then delete the app itself
|
||||||
|
String deleteAppSql = "DELETE FROM sys_app WHERE id = ?";
|
||||||
|
jdbcTemplate.update(deleteAppSql, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -96,8 +96,10 @@ public class UserRepository {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Integer count() {
|
||||||
|
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM sys_user", Integer.class);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateUserGroupMappings(Integer userId, List<Integer> groups) {
|
private void updateUserGroupMappings(Integer userId, List<Integer> groups) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
package de.avatic.lcc.service.apps;
|
package de.avatic.lcc.service.apps;
|
||||||
|
|
||||||
import de.avatic.lcc.dto.users.AppDTO;
|
import de.avatic.lcc.dto.configuration.apps.AppDTO;
|
||||||
import de.avatic.lcc.model.db.users.App;
|
import de.avatic.lcc.model.db.users.App;
|
||||||
import de.avatic.lcc.repositories.users.AppRepository;
|
import de.avatic.lcc.repositories.users.AppRepository;
|
||||||
import de.avatic.lcc.service.transformer.apps.AppTransformer;
|
import de.avatic.lcc.service.transformer.apps.AppTransformer;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AppsService {
|
public class AppsService {
|
||||||
|
|
@ -30,11 +33,35 @@ public class AppsService {
|
||||||
public AppDTO updateApp(AppDTO dto) {
|
public AppDTO updateApp(AppDTO dto) {
|
||||||
|
|
||||||
var newApp = dto.getId() == null;
|
var newApp = dto.getId() == null;
|
||||||
var id = !newApp ? dto.getId() : appRepository.update(appTransformer.toAppEntity(dto));
|
String appSecret = null;
|
||||||
|
|
||||||
|
if(newApp) {
|
||||||
|
dto.setClientId(generateAppId());
|
||||||
|
appSecret = generateAppSecret();
|
||||||
|
dto.setClientSecret(passwordEncoder.encode(appSecret));
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = appRepository.update(appTransformer.toAppEntity(dto));
|
||||||
|
|
||||||
|
if(newApp) {
|
||||||
|
dto.setId(id);
|
||||||
|
dto.setClientSecret(appSecret);
|
||||||
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String generateAppId() {
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateAppSecret() {
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
byte[] bytes = new byte[32];
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteApp(Integer id) {
|
public void deleteApp(Integer id) {
|
||||||
appRepository.delete(id);
|
appRepository.delete(id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package de.avatic.lcc.repositories.users;
|
package de.avatic.lcc.service.apps;
|
||||||
|
|
||||||
import de.avatic.lcc.model.db.users.App;
|
import de.avatic.lcc.model.db.users.App;
|
||||||
import de.avatic.lcc.model.db.users.Group;
|
import de.avatic.lcc.model.db.users.Group;
|
||||||
|
|
@ -12,7 +12,6 @@ import javax.crypto.SecretKey;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class JwtTokenService {
|
public class JwtTokenService {
|
||||||
|
|
@ -28,7 +27,6 @@ public class JwtTokenService {
|
||||||
|
|
||||||
public String createApplicationToken(App app, long expiration) {
|
public String createApplicationToken(App app, long expiration) {
|
||||||
|
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.issuer(baseUrl)
|
.issuer(baseUrl)
|
||||||
.subject(app.getClientId())
|
.subject(app.getClientId())
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package de.avatic.lcc.service.transformer.apps;
|
package de.avatic.lcc.service.transformer.apps;
|
||||||
|
|
||||||
import de.avatic.lcc.dto.users.AppDTO;
|
import de.avatic.lcc.dto.configuration.apps.AppDTO;
|
||||||
import de.avatic.lcc.model.db.users.App;
|
import de.avatic.lcc.model.db.users.App;
|
||||||
import de.avatic.lcc.model.db.users.Group;
|
import de.avatic.lcc.model.db.users.Group;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ CREATE TABLE IF NOT EXISTS `sys_user_node`
|
||||||
FOREIGN KEY (`country_id`) REFERENCES `country` (`id`)
|
FOREIGN KEY (`country_id`) REFERENCES `country` (`id`)
|
||||||
) COMMENT 'Contains user generated logistic nodes';
|
) COMMENT 'Contains user generated logistic nodes';
|
||||||
|
|
||||||
-- Main table for user information
|
-- Main table for app information
|
||||||
CREATE TABLE IF NOT EXISTS `sys_app`
|
CREATE TABLE IF NOT EXISTS `sys_app`
|
||||||
(
|
(
|
||||||
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
|
@ -147,7 +147,7 @@ CREATE TABLE IF NOT EXISTS `sys_app`
|
||||||
) COMMENT 'Stores basic information about external applications';
|
) COMMENT 'Stores basic information about external applications';
|
||||||
|
|
||||||
|
|
||||||
-- Junction table for user-group assignments
|
-- Junction table for app-group assignments
|
||||||
CREATE TABLE IF NOT EXISTS `sys_app_group_mapping`
|
CREATE TABLE IF NOT EXISTS `sys_app_group_mapping`
|
||||||
(
|
(
|
||||||
`app_id` INT NOT NULL,
|
`app_id` INT NOT NULL,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue