3 Commits

Author SHA1 Message Date
Adrian Zürcher
df580d98c0 new release
All checks were successful
Build Quasar SPA and Go Backend for memberApp / build-spa (push) Successful in 2m19s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, .exe, windows) (push) Successful in 5m32s
Build Quasar SPA and Go Backend for memberApp / build-backend (amd64, , linux) (push) Successful in 5m34s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm, 6, , linux) (push) Successful in 5m31s
Build Quasar SPA and Go Backend for memberApp / build-backend (arm64, , linux) (push) Successful in 5m33s
2025-11-08 12:14:05 +01:00
Adrian Zürcher
06afdf4349 add feature to set user defined expiration on every user close #4 2025-11-08 12:09:56 +01:00
Adrian Zürcher
db96732a62 fix functions existing and delete 2025-11-07 08:34:53 +01:00
17 changed files with 121 additions and 40 deletions

View File

@@ -3,8 +3,8 @@ module backend
go 1.24.5
require (
gitea.tecamino.com/paadi/access-handler v1.0.13
gitea.tecamino.com/paadi/memberDB v1.0.18
gitea.tecamino.com/paadi/access-handler v1.0.19
gitea.tecamino.com/paadi/memberDB v1.0.21
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
github.com/gin-contrib/cors v1.7.6
@@ -13,7 +13,7 @@ require (
)
require (
gitea.tecamino.com/paadi/dbHandler v1.0.4 // indirect
gitea.tecamino.com/paadi/dbHandler v1.0.8 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect

View File

@@ -1,9 +1,9 @@
gitea.tecamino.com/paadi/access-handler v1.0.13 h1:2BqLo+cmF7ijk8XMnTdNuUKGbWPRsW6jMbk44FMCpyY=
gitea.tecamino.com/paadi/access-handler v1.0.13/go.mod h1:w71lpnuu5MgAWG3oiI9vsY2dWi4njF/iPrM/xV/dbBQ=
gitea.tecamino.com/paadi/dbHandler v1.0.4 h1:ctnaec0GDdtw3gRQdUISVDYLJ9x+vt50VW41OemfhD4=
gitea.tecamino.com/paadi/dbHandler v1.0.4/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
gitea.tecamino.com/paadi/memberDB v1.0.18 h1:S774DtR5t6jpkRfa6uHlrnvTjabHG2KSjfbMmjGkdTM=
gitea.tecamino.com/paadi/memberDB v1.0.18/go.mod h1:iLm7nunVRzqJK8CV4PJVuWIhgPlQjNIaeOkmtfK5fMg=
gitea.tecamino.com/paadi/access-handler v1.0.19 h1:L51Qg5RNjdIGeQsHwGUTV+ADRpUqPt3qdnu7A7UQbsw=
gitea.tecamino.com/paadi/access-handler v1.0.19/go.mod h1:wKsB5/Rvaj580gdg3+GbUf5V/0N00XN6cID+C/8135M=
gitea.tecamino.com/paadi/dbHandler v1.0.8 h1:ZWSBM/KFtLwTv2cBqwK1mOxWAxAfL0BcWEC3kJ9JALU=
gitea.tecamino.com/paadi/dbHandler v1.0.8/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
gitea.tecamino.com/paadi/memberDB v1.0.21 h1:kGQe5fUOc50oQj8caWcjnmmxJaSPuQEjeSG5qDT9Iz4=
gitea.tecamino.com/paadi/memberDB v1.0.21/go.mod h1:/Af2OeJIHq+8kE5L5DlJxhSJjB75eWBcKRpkxi+n9bU=
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1 h1:vAq7mwUxlxJuLzCQSDMrZCwo8ky5usWi9Qz+UP+WnkI=
gitea.tecamino.com/paadi/tecamino-dbm v0.1.1/go.mod h1:+tmf1rjPaKEoNeUcr1vdtoFIFweNG3aUGevDAl3NMBk=
gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE=

View File

@@ -1,6 +1,6 @@
{
"name": "lightcontrol",
"version": "1.0.5",
"version": "1.0.7",
"description": "A Tecamino App",
"productName": "Member Database",
"author": "A. Zuercher",

View File

@@ -111,3 +111,5 @@ responsibleAdded: Veratwortläche hinzuegfüegt
responsiblesAdded: Veratwortläche hinzuegfüegt
deleteResponsible: Veratwortläche entfernt
deleteResponsibles: Veratwortläche entfernt
expiration: Ablauf
never: Nie

View File

@@ -111,3 +111,5 @@ responsibleAdded: Veratwortläche hinzuegfüegt
responsiblesAdded: Verantwortliche hinzuegfüegt
deleteResponsible: Verantwortliche entfernt
deleteResponsibles: Verantwortliche entfernt
expiration: Ablauf
never: Nie

View File

@@ -111,3 +111,5 @@ responsibleAdded: Responsible hinzuegfüegt
responsiblesAdded: Responsibles hinzuegfüegt
deleteResponsible: Responsible deleted
deleteResponsibles: Responsibles deleted
expiration: Expiration
never: Never

View File

@@ -55,6 +55,21 @@ appApi.interceptors.response.use(
// Handle unauthorized responses
if (error.response?.status === 401 && !originalRequest._retry) {
const data = error.response?.data;
const serverMessage =
typeof data === 'object' && data !== null && 'message' in data
? (data as { message: string }).message
: undefined;
if (['no refresh token', 'is expired'].some((msg) => serverMessage?.includes(msg))) {
console.warn('[Axios] No refresh token — logging out');
try {
await logout();
} catch (logoutErr) {
console.error('[Axios] Logout failed:', logoutErr);
}
throw new Error('Session expired: no refresh token');
}
if (isRefreshing) {
// Wait until refresh completes
return new Promise<AxiosResponse>((resolve, reject) => {

View File

@@ -6,7 +6,7 @@ export default boot(async ({ router }) => {
// Restore logic after router is ready but before navigation
router.isReady().then(() => {
const lastRoute = sessionStorage.getItem('lastRoute');
const lastRoute = localStorage.getItem('lastRoute');
const currentPath = router.currentRoute.value.fullPath;
// Restore only if:
@@ -26,7 +26,7 @@ export default boot(async ({ router }) => {
router.afterEach((to) => {
// Don't save login page as "last route"
if (to.path !== '/login' && to.path !== '/') {
sessionStorage.setItem('lastRoute', to.fullPath);
localStorage.setItem('lastRoute', to.fullPath);
}
});
});

View File

@@ -18,7 +18,15 @@
<q-btn class="q-ma-md" color="primary" no-caps @click="addAttendees">{{ localTitle }}</q-btn>
</div>
</DialogFrame>
<EditAllDialog ref="newEventRef" v-on:update="(val) => resolveNewEvent(val)"></EditAllDialog>
<EditAllDialog
ref="newEventRef"
v-on:update="
(val) => {
resolveNewEvent(val);
NotifyResponse($t('memberUpdated'));
}
"
></EditAllDialog>
</template>
<script setup lang="ts">

View File

@@ -2,24 +2,46 @@
<DialogFrame ref="dialog" :header-title="'Edit ' + localTitle">
<div class="row justify-center">
<q-input autofocus :label="localTitle" filled v-model="value" @keyup.enter="save">
<template v-if="['firstVisit', 'lastVisit', 'date'].includes(localField)" v-slot:prepend>
<template
v-if="['firstVisit', 'lastVisit', 'date', 'expiration'].includes(localField)"
v-slot:prepend
>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="value" mask="YYYY-MM-DD HH:mm:ss">
<div class="row items-center justify-end">
<q-btn :label="$t('now')" color="primary" no-caps flat @click="setTimeNow" />
<q-btn
v-if="localField"
:label="$t('never')"
color="primary"
no-caps
flat
@click="value = 'never'"
/>
<q-btn no-caps v-close-popup :label="$t('close')" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
<template v-if="['firstVisit', 'lastVisit', 'date'].includes(localField)" v-slot:append>
<template
v-if="['firstVisit', 'lastVisit', 'date', 'expiration'].includes(localField)"
v-slot:append
>
<q-icon name="access_time" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-time with-seconds v-model="value" mask="YYYY-MM-DD HH:mm:ss" format24h>
<div class="row items-center justify-end">
<q-btn :label="$t('now')" color="primary" no-caps flat @click="setTimeNow" />
<q-btn
v-if="localField"
:label="$t('never')"
color="primary"
no-caps
flat
@click="value = 'never'"
/>
<q-btn no-caps v-close-popup :label="$t('close')" color="primary" flat />
</div>
</q-time>
@@ -40,7 +62,6 @@ import { ref } from 'vue';
import { appApi } from 'src/boot/axios';
import type { Member } from 'src/vueLib/models/member';
import { useNotify } from 'src/vueLib/general/useNotify';
import { i18n } from 'src/boot/lang';
const dialog = ref();
const localMember = ref();
@@ -51,7 +72,6 @@ const value = ref('');
const props = defineProps({
endpoint: {
type: String,
required: true,
},
});
@@ -74,6 +94,12 @@ function save() {
dialog.value.close();
return;
}
if (!props.endpoint) {
localMember.value[localField.value] = value.value;
emit('update', localMember.value);
return;
}
payload = [
{
id: localMember.value.id,
@@ -85,7 +111,6 @@ function save() {
.post(props.endpoint, payload)
.then(() => {
emit('update');
NotifyResponse(i18n.global.t('memberUpdated'));
dialog.value.close();
})
.catch((err) => {

View File

@@ -91,10 +91,11 @@
v-model="localUser.role"
></q-select>
<q-input
class="col-5"
class="col-5 q-mt-xl"
:label="$t('expires')"
filled
v-model="localUser.expires"
type="datetime-local"
v-model="localUser.expiration"
></q-input>
</div>
</div>
@@ -131,7 +132,6 @@ const localUser = ref<User>({
user: '',
email: '',
role: '',
expires: '',
});
const props = defineProps({
@@ -156,7 +156,6 @@ async function open(user: User | null) {
user: '',
email: '',
role: '',
expires: '',
};
newUser.value = true;
}

View File

@@ -21,7 +21,7 @@ onMounted(() => {
const forwardToPage = async () => {
await nextTick();
const lastRoute = sessionStorage.getItem('lastRoute') || '/members';
const lastRoute = localStorage.getItem('lastRoute') || '/members';
await router.push(lastRoute);
};
</script>

View File

@@ -48,7 +48,7 @@ export function useLogin() {
});
userStore.clearUser();
sessionStorage.clear();
localStorage.clear();
stopRefreshInterval();
}

View File

@@ -5,7 +5,7 @@ export interface User {
user: string;
email: string;
role: string;
expires: string;
expiration?: string;
password?: string;
settings?: Settings;
}

View File

@@ -116,6 +116,7 @@
<q-td :props="props">
<q-select
v-if="responsibles.length > 0"
:readonly="!user.isPermittedTo('members', 'write')"
:options="responsibles"
:option-label="(opt) => opt.firstName + ' ' + opt.lastName"
v-model="props.row.responsiblePerson"
@@ -163,12 +164,12 @@
ref="editOneDialog"
endpoint="members/edit"
query-id
v-on:update="updateMembers"
v-on:update="updateMember"
></EditOneDialog>
<EditAllDialog
ref="editAllDialog"
:responsibles="responsibles"
v-on:update="updateMembers"
v-on:update="updateMember"
></EditAllDialog>
<OkDialog
ref="okDialog"
@@ -325,11 +326,13 @@ function removeMember(...removeMembers: Members) {
});
}
function updateMember(member: Member) {
function updateMember(member: Member | null) {
if (!member) NotifyResponse(i18n.global.t('memberUpdated'));
appApi
.post('/members/edit', [member])
.then(() => NotifyResponse(i18n.global.t('memberUpdated')))
.catch((err) => NotifyResponse(err, 'error'));
updateMembers().catch((err) => NotifyResponse(err, 'error'));
}
function addToEvent() {

View File

@@ -37,6 +37,14 @@ export function useUserTable() {
sortable: true,
style: 'width: 120px; max-width: 120px;',
},
{
name: 'expiration',
align: 'left' as const,
label: i18n.global.t('expiration'),
field: 'expiration',
sortable: true,
style: 'width: 120px; max-width: 120px;',
},
{ name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' },
]);

View File

@@ -67,17 +67,7 @@
</q-input>
</template>
<template v-slot:body-cell="props">
<q-td v-if="props.col.name === 'role'" :props="props">
<q-select
:readonly="!user.isPermittedTo('userSettings', 'write') || !autorized(props.row)"
dense
v-model="props.row.role"
:options="localRoles"
@update:model-value="updateUser(props.row)"
></q-select>
</q-td>
<q-td
v-else
:props="props"
:style="
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
@@ -93,6 +83,34 @@
{{ props.value }}
</q-td>
</template>
<template v-slot:body-cell-role="props">
<q-td :props="props">
<q-select
:readonly="!user.isPermittedTo('userSettings', 'write') || !autorized(props.row)"
dense
v-model="props.row.role"
:options="localRoles"
@update:model-value="updateUser(props.row)"
></q-select>
</q-td>
</template>
<template v-slot:body-cell-expiration="props">
<q-td
:props="props"
:style="
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
? 'cursor: pointer'
: ''
"
@click="
autorized(props.row) && user.isPermittedTo('userSettings', 'write')
? openSingleValueDialog(props.col.label, props.col.name, props.row)
: ''
"
>
{{ props.value === 'never' ? $t('never') : props.value }}
</q-td>
</template>
<template v-slot:body-cell-option="props">
<q-td :props="props">
<q-btn
@@ -112,9 +130,8 @@
</div>
<EditOneDialog
ref="editOneDialog"
endpoint="users/edit"
query-id
v-on:update="updateUsers"
v-on:update="(val) => updateUser(val)"
></EditOneDialog>
<EditAllDialog ref="editAllDialog" :roles="localRoles" v-on:update="updateUsers"></EditAllDialog>
<OkDialog