Compare commits

...

8 Commits

8 changed files with 154 additions and 9 deletions

View File

@ -1,6 +1,6 @@
{ {
"license": "MIT", "license": "MIT",
"version": "1.0.0", "version": "1.1.0",
"name": "@flaschengeist/api", "name": "@flaschengeist/api",
"author": "Tim Gröger <flaschengeist@wu5.de>", "author": "Tim Gröger <flaschengeist@wu5.de>",
"homepage": "https://flaschengeist.dev/Flaschengeist", "homepage": "https://flaschengeist.dev/Flaschengeist",

61
api/src/stores/apiKeys.ts Normal file
View File

@ -0,0 +1,61 @@
import { defineStore } from 'pinia';
import { api } from '../internal';
import { isAxiosError, useMainStore } from '.';
export const useApiKeyStore = defineStore({
id: 'apiKeys',
state: () => ({
apiKeys: [] as FG.ApiKey[],
}),
getters: {},
actions: {
async getApiKeys(): Promise<FG.ApiKey[]> {
try {
const mainStore = useMainStore();
const { data } = await api.get<FG.ApiKey[]>(
`/users/${mainStore.currentUser.userid}/api_keys`
);
this.apiKeys = data;
return data;
} catch (error) {
return [] as FG.ApiKey[];
}
},
async deleteApiKey(id: number): Promise<boolean> {
const mainStore = useMainStore();
try {
await api.delete(`/users/${mainStore.currentUser.userid}/api_keys/${id}`);
this.apiKeys = this.apiKeys.filter((apiKey: FG.ApiKey) => apiKey.id !== id);
return true;
} catch (error) {
// Ignore 401, as this means we are already logged out, throw all other
if (!isAxiosError(error, 401)) throw error;
}
return false;
},
async createApiKey(apiKey: FG.ApiKey): Promise<FG.ApiKey> {
const mainStore = useMainStore();
try {
const { data } = await api.post<FG.ApiKey>(
`/users/${mainStore.currentUser.userid}/api_keys`,
apiKey
);
this.apiKeys.push(data);
return data;
} catch (error) {
throw error;
}
},
},
});

View File

@ -21,3 +21,4 @@ export function isAxiosError(error: unknown, status?: number) {
export * from './main'; export * from './main';
export * from './session'; export * from './session';
export * from './user'; export * from './user';
export * from './apiKeys';

View File

@ -1,6 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from '../internal'; import { api } from '../internal';
import { isAxiosError, useMainStore } from '.'; import { isAxiosError, useMainStore } from '.';
import { DisplayNameMode } from '@flaschengeist/users';
export function fixUser(u?: FG.User) { export function fixUser(u?: FG.User) {
return !u ? u : Object.assign(u, { birthday: u.birthday ? new Date(u.birthday) : undefined }); return !u ? u : Object.assign(u, { birthday: u.birthday ? new Date(u.birthday) : undefined });
@ -22,6 +23,7 @@ export const useUserStore = defineStore({
state: () => ({ state: () => ({
roles: [] as FG.Role[], roles: [] as FG.Role[],
permissions: [] as FG.Permission[], permissions: [] as FG.Permission[],
userSettings: {} as FG.UserSettings,
// list of all users, include deleted ones, use `users` getter for list of active ones // list of all users, include deleted ones, use `users` getter for list of active ones
_users: [] as FG.User[], _users: [] as FG.User[],
// Internal flags for deciding if lists need to force-loaded // Internal flags for deciding if lists need to force-loaded
@ -31,7 +33,42 @@ export const useUserStore = defineStore({
getters: { getters: {
users(state) { users(state) {
return state._users.filter((u) => !u.deleted); const u = state._users.filter((u) => !u.deleted);
switch (this.userSettings['display_name']) {
case DisplayNameMode.FIRSTNAME_LASTNAME || DisplayNameMode.FIRSTNAME:
u.sort((a, b) => {
const a_lastname = a.lastname.toLowerCase();
const b_lastname = b.lastname.toLowerCase();
const a_firstname = a.firstname.toLowerCase();
const b_firstname = b.firstname.toLowerCase();
if (a_firstname === b_firstname) {
return a_lastname < b_lastname ? -1 : 1;
}
return a_firstname < b_firstname ? -1 : 1;
});
break;
case <string>DisplayNameMode.DISPLAYNAME:
u.sort((a, b) => {
const a_displayname = a.display_name.toLowerCase();
const b_displayname = b.display_name.toLowerCase();
return a_displayname < b_displayname ? -1 : 1;
});
break;
default:
u.sort((a, b) => {
const a_lastname = a.lastname.toLowerCase();
const b_lastname = b.lastname.toLowerCase();
const a_firstname = a.firstname.toLowerCase();
const b_firstname = b.firstname.toLowerCase();
if (a_lastname === b_lastname) {
return a_firstname < b_firstname ? -1 : 1;
}
return a_lastname < b_lastname ? -1 : 1;
});
}
return u;
}, },
}, },
@ -207,5 +244,35 @@ export const useUserStore = defineStore({
await api.delete(`/roles/${role}`); await api.delete(`/roles/${role}`);
this.roles = this.roles.filter((r) => r.id !== role); this.roles = this.roles.filter((r) => r.id !== role);
}, },
/** Get Settings for display name mode
* @param force If set to true a fresh list is loaded from backend even when a local copy is available
* @throws Probably an AxiosError if request failed
* @returns Settings for display name mode
*/
async getDisplayNameModeSetting(force = false): Promise<string> {
const mainStore = useMainStore();
if (force) {
const { data } = await api.get<{ data: string }>(
`users/${mainStore.currentUser.userid}/setting/display_name_mode`
);
this.userSettings['display_name'] = data.data;
}
return this.userSettings['display_name'];
},
/** Set Settings for display name mode
* @param mode New display name mode
* @throws Probably an AxiosError if request failed
* @returns Settings for display name mode
*/
async setDisplayNameModeSetting(mode: string): Promise<string> {
const mainStore = useMainStore();
await api.put(`users/${mainStore.currentUser.userid}/setting/display_name_mode`, {
data: mode,
});
this.userSettings['display_name'] = mode;
return mode;
},
}, },
}); });

View File

@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"version": "2.0.0", "version": "2.1.0",
"productName": "flaschengeist-frontend", "productName": "flaschengeist-frontend",
"name": "flaschengeist", "name": "flaschengeist",
"author": "Tim Gröger <flaschengeist@wu5.de>", "author": "Tim Gröger <flaschengeist@wu5.de>",

View File

@ -3,4 +3,4 @@ module.exports = [
// '@flaschengeist/balance', // '@flaschengeist/balance',
// '@flaschengeist/schedule', // '@flaschengeist/schedule',
// '@flaschengeist/pricelist', // '@flaschengeist/pricelist',
] '@flaschengeist/schedule',

View File

@ -15,12 +15,18 @@ const { configure } = require('quasar/wrappers');
const operation = () => { const operation = () => {
const custom_plgns = require('./plugin.config.js'); const custom_plgns = require('./plugin.config.js');
const required_plgns = require('./src/vendor-plugin.config.js'); const required_plgns = require('./src/vendor-plugin.config.js');
const plugins = [...custom_plgns, ...required_plgns].map((v) => `import("${v}").catch(() => "${v}")`); const plugins = [...custom_plgns, ...required_plgns].map(
const replace = new ReplaceOperation('all', `\\/\\* *INSERT_PLUGIN_LIST *\\*\\/`, `${plugins.join(', ')}`); (v) => `import("${v}").catch(() => "${v}")`
);
const replace = new ReplaceOperation(
'all',
`\\/\\* *INSERT_PLUGIN_LIST *\\*\\/`,
`${plugins.join(', ')}`
);
return replace; return replace;
}; };
module.exports = configure(function(/* ctx */) { module.exports = configure(function (/* ctx */) {
return { return {
// https://quasar.dev/quasar-cli/supporting-ts // https://quasar.dev/quasar-cli/supporting-ts
supportTS: { supportTS: {
@ -50,7 +56,7 @@ module.exports = configure(function(/* ctx */) {
// 'ionicons-v5', // 'ionicons-v5',
// 'line-awesome', // 'line-awesome',
// 'material-icons', // 'material-icons',
'mdi-v6', 'mdi-v7',
// 'themify', // 'themify',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
@ -60,7 +66,7 @@ module.exports = configure(function(/* ctx */) {
// Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build // Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
build: { build: {
vueRouterMode: 'history', // available values: 'hash', 'history' vueRouterMode: 'history', // available values: 'hash', 'history'
//publicPath: 'flaschengeist2',
// transpile: false, // transpile: false,
// Add dependencies for transpiling with Babel (Array of string/regex) // Add dependencies for transpiling with Babel (Array of string/regex)
@ -214,5 +220,8 @@ module.exports = configure(function(/* ctx */) {
// chainWebpack also available besides this extendWebpack // chainWebpack also available besides this extendWebpack
}, },
}, },
bin: {
linuxAndroidStudio: '/home/crimsen/.local/share/JetBrains/Toolbox/scripts/studio',
},
}; };
}); });

View File

@ -234,6 +234,12 @@ function loadPlugin(
Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets); Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets);
} }
if (!plugin.settingWidgets) plugin.settingWidgets = [];
if (plugin.settingWidgets.length > 0) {
plugin.settingWidgets.forEach((widget) => (widget.name = plugin.id + '.' + widget.name));
Array.prototype.push.apply(loadedPlugins.settingWidgets, plugin.settingWidgets);
}
loadedPlugins.plugins.push({ loadedPlugins.plugins.push({
id: plugin.id, id: plugin.id,
name: plugin.name, name: plugin.name,
@ -252,6 +258,7 @@ export async function loadPlugins(backend: FG.Backend, baseRoutes: RouteRecordRa
shortcuts: [], shortcuts: [],
outerShortcuts: [], outerShortcuts: [],
widgets: [], widgets: [],
settingWidgets: [],
}; };
// Wait for all plugins to be loaded // Wait for all plugins to be loaded