settings, admin panel

This commit is contained in:
lesion 2024-09-25 23:22:52 +02:00
parent a4fafc180e
commit d0745115c7
No known key found for this signature in database
GPG key ID: 352918250B012177
13 changed files with 209 additions and 40 deletions

View file

@ -1,18 +1,30 @@
<script setup lang="ts">
const { Settings } = useSettings()
const { Settings, saveSetting } = useSettings()
function getSetting(key: string) {
return Setting[key]
return Settings.value[key]
}
function setSetting(key: string, value: string) {
return saveSetting(key, value)
}
const localSettings = { ...Settings.value }
</script>
<template>
<v-container>
<v-card-title>{{ $t('common.settings') }}</v-card-title>
<v-card-text>
<v-text-field :modelValue="getSetting('title')"
@update:model-value="v => setSetting('title')"
:label="$t('common.title')"
@update:model-value="v => setSetting('title', v)"
:hint="$t('admin.title_description')" persistent-hint />
<v-text-field class='mt-5' :modelValue="getSetting('description')"
:label="$t('common.description')"
@update:model-value="v => setSetting('description', v)"
:hint="$t('admin.description_description')" persistent-hint />
</v-card-text>
</v-container>
</template>

View file

@ -1,5 +1,5 @@
<template>
<article class='h-event' itemscope itemtype="https://schema.org/Event">
<article class='h-event b-1 border-1' itemscope itemtype="https://schema.org/Event">
<nuxt-link :to='`/event/${event.slug || event.id}`' itemprop="url">
<!-- <nuxt-picture/> -->

View file

@ -1,9 +1,8 @@
export default () => {
type SettingsType =
| { [key: string]: string }
// import type { KeyValueType } from "~/server/utils/settings"
// const { data: Settings } = await useFetch('/api/setting')
const Settings = useState<SettingsType>('settings', () => reactive({ }))
export default () => {
const Settings = useState<KeyValueType>('settings', () => reactive({ }))
const loadSettings = async () => {
Settings.value = await $fetch('/api/settings')
@ -11,9 +10,8 @@ export default () => {
const saveSetting = async (key: string, value: string) => {
Settings.value[key] = value
await $fetch(`/api/setting`, { method: 'POST', body: { key, value } })
await $fetch(`/api/settings`, { method: 'PUT', body: { key, value } })
}
return { Settings, saveSetting, loadSettings }
}

View file

@ -4,7 +4,13 @@ import locales from './locales/index'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: { enabled: true },
devtools: {
enabled: true,
timeline: {
enabled: true,
},
},
modules: ["vuetify-nuxt-module", '@nuxtjs/i18n'],
vuetify: {
vuetifyOptions: './vuetify.options.js'

View file

@ -33,22 +33,23 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxtjs/i18n": "^8.5.0",
"@nuxtjs/i18n": "^8.5.3",
"@sequelize/core": "^7.0.0-alpha.37",
"@sequelize/sqlite3": "^7.0.0-alpha.41",
"nuxt": "^3.12.4",
"vue": "latest",
"vuetify-nuxt-module": "^0.16.1"
"@sequelize/sqlite3": "^7.0.0-alpha.42",
"nuxt": "^3.13.2",
"vue": "3.5.6",
"vuetify-nuxt-module": "^0.18.3"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.5.0",
"@nuxt/eslint-config": "^0.5.7",
"@nuxtjs/eslint-module": "^4.1.0",
"bcrypt": "^5.1.1",
"eslint": "^9.9.0",
"eslint": "^9.10.0",
"eslint-plugin-nuxt": "^4.0.0",
"linkifyjs": "^4.1.3",
"prettier": "^3.3.3",
"sqlite3": "^5.1.7"
"sqlite3": "^5.1.7",
"zod": "^3.23.8"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View file

@ -1,26 +1,39 @@
<script setup lang="ts">
const { loadSettings } = useSettings()
loadSettings()
const { isAdmin } = useAuth()
const selectedTab = ref()
const selectedTab = ref('')
</script>
<template>
<v-container>
<v-card>
<!-- admin alert -->
<AdminSetupAlert v-if="isAdmin" />
<v-tabs v-model="selectedTab" show-arrows bg-color="primary">
<v-tab href="#settings" v-if="isAdmin" value="settings"> {{ $t('common.settings') }}</v-tab>
</v-tabs>
<v-card-text>
<AdminSetupAlert v-if="isAdmin" />
<v-tabs v-model="selectedTab" show-arrows bg-color="primary">
<!-- <v-tab href="#settings" v-if="isAdmin" value="settings"> {{ $t('common.settings') }}</v-tab> -->
<v-tab href="#settings" value="settings"> {{ $t('common.settings') }}</v-tab>
<v-tab href="#events" value="events"> {{ $t('common.events') }}</v-tab>
<v-tab href="#users" value="users"> {{ $t('common.users') }}</v-tab>
<v-tab href="#collections" value="collections"> {{ $t('common.collections') }}</v-tab>
<v-tab href="#federation" value="federation"> {{ $t('common.federation') }}</v-tab>
<v-tab href="#moderation" value="moderation"> {{ $t('common.moderation') }}</v-tab>
<v-tab href="#places" value="places"> {{ $t('common.places') }}</v-tab>
<v-tab href="#tags" value="tags"> {{ $t('common.tags') }}</v-tab>
<v-tab href="#theme" value="theme"> {{ $t('common.theme') }}</v-tab>
<v-tab href="#geolocation" value="geolocation"> {{ $t('common.geolocation') }}</v-tab>
<v-tab href="#announcements" value="announcements"> {{ $t('common.announcements') }}</v-tab>
<v-tab href="#plugins" value="plugins"> {{ $t('common.plugins') }}</v-tab>
</v-tabs>
<v-card-text>
<v-tabs-window v-model="selectedTab">
<v-tabs-window-item value="settings">
<AdminSettings />
</v-tabs-window-item>
</v-tabs-window>
</v-card-text>
</v-card-text>
</v-card>
</v-container>
</template>

View file

@ -16,11 +16,11 @@ const { data: events } = await useFetch<Event[]>('/api/events')
<!-- Events -->
<section id='events' class='mt-sm-4 mt-2'>
<v-lazy class='event v-card'
<Event v-for='(event, idx) in events' :key='event.id' :event='event' :lazy='idx>9' />
<!-- <v-lazy class='event v-card'
v-for='(event, idx) in events' :key='event.id'
:options="{ threshold: .5, rootMargin: '500px' }">
<Event :event='event' :lazy='idx>9' />
</v-lazy>
</v-lazy> -->
</section>
<!-- <section class='text-center' v-else> -->
<!-- <v-progress-circular class='mt-5 justify-center align-center mx-auto' color='primary' indeterminate model-value='20' /> -->
@ -28,6 +28,14 @@ const { data: events } = await useFetch<Event[]>('/api/events')
</v-container>
</template>
<style>
#events {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(350px,1fr));
}
</style>
<!-- <script setup lang="ts"> -->
// import { mapState, mapActions, mapGetters } from 'vuex'
// import { DateTime } from 'luxon'

82
pages/tag/[tag].vue Normal file
View file

@ -0,0 +1,82 @@
s<script setup lang="ts">
import type { Tag } from '#build/types/nitro-imports';
import useSettings from '~/composables/useSettings'
const { Settings } = useSettings()
const route = useRoute()
const { data: tag } = await useFetch<Tag>(`/api/tag/${route.params.tag}`)
if (!tag) {
throw createError({ status: 404, statusMessage: 'Tag not found'})
}
useHead( () => {
const title = `${Settings.value.title} - ${tag.value?.tag}`
return {
title,
link: [
// { rel: 'alternate', type: 'application/rss+xml', title, href: this.settings.baseurl + `/feed/rss/place/${this.place.name}` },
// { rel: 'alternate', type: 'text/calendar', title, href: this.settings.baseurl + `/feed/ics/place/${this.place.name}` }
],
}
})
</script>
<template>
<v-container id='home' class='px-2 px-sm-6 pt-0'>
<h1 class='d-block text-h4 font-weight-black text-center text-uppercase mt-10 mx-auto w-100 text-underline'>
<u>{{ tag?.tag}}</u>
</h1>
<!-- <span v-if='place?.name!=="online"' class="d-block text-subtitle text-center w-100">{{ place?.address }}</span> -->
<!-- Events -->
<!-- <div id="events" class='mt-14'>
<v-lazy class='event v-card' :value='idx<9' v-for='(event, idx) in events' :key='event.id' :min-height='hide_thumbs ? 105 : undefined' :options="{ threshold: .5, rootMargin: '500px' }" :class="{ 'theme--dark': is_dark }">
<Event :event='event' :lazy='idx > 9' />
</v-lazy>
</div> -->
</v-container>
</template>
<!-- <script>
import { mapState, mapGetters } from 'vuex'
import Event from '@/components/Event'
import HowToArriveNav from '@/components/HowToArriveNav.vue'
export default {
name: 'Place',
components: {
Event,
HowToArriveNav,
[process.client && 'Map']: () => import('@/components/Map.vue')
},
data() {
return { mapHeight: "14rem" }
},
head() {
const title = `${this.settings.title} - ${this.place.name}`
return {
title,
link: [
{ rel: 'alternate', type: 'application/rss+xml', title, href: this.settings.baseurl + `/feed/rss/place/${this.place.name}` },
{ rel: 'alternate', type: 'text/calendar', title, href: this.settings.baseurl + `/feed/ics/place/${this.place.name}` }
],
}
},
computed: {
...mapState(['settings']),
...mapGetters(['hide_thumbs', 'is_dark']),
},
async asyncData({ $axios, params, error }) {
try {
const events = await $axios.$get(`/place/${encodeURIComponent(params.place)}`)
return events
} catch (e) {
error({ statusCode: 404, message: 'Place not found!' })
}
}
}
</script>
-->

View file

@ -0,0 +1,10 @@
import { KeyValueType } from "~/server/utils/settings"
export default defineEventHandler( async event => {
const publicSettings = await Setting.findAll({ where: { is_secret: false }})
const ret:KeyValueType = {}
publicSettings.forEach(setting => {
ret[setting.key] = setting.value
})
return ret
})

View file

@ -1,3 +1,6 @@
export default defineEventHandler(event => {
export default defineEventHandler(async event => {
const setting = await readValidatedBody(event, body => settingSchema.parse(body))
setSetting(setting)
// return Setting.upsert(data)
})

View file

@ -1,3 +0,0 @@
export default defineEventHandler( event => {
return Setting.findAll({ where: { is_secret: false }})
})

View file

@ -31,6 +31,7 @@ import {
AllowNull,
BelongsToMany,
BelongsTo,
Default,
} from "@sequelize/core/decorators-legacy"
// import type { SqliteDialect } from "@sequelize/sqlite3";
@ -375,9 +376,10 @@ export class Setting extends Model<
declare key: string
@Attribute(DataTypes.JSON)
declare value: boolean
declare value: string
@Attribute(DataTypes.BOOLEAN)
@Default(false)
declare is_secret: boolean
}

37
server/utils/settings.ts Normal file
View file

@ -0,0 +1,37 @@
export type KeyValueType =
| { [key: string]: string }
import { z } from 'zod'
// import { SettingType } from './sequelize'
export const settingSchema = z.object({
key: z.string(),
is_secret: z.boolean().default(false),
value: z.string()
})
export type SettingType = z.infer<typeof settingSchema>
export const publicSettings:KeyValueType = {}
export const secretSettings:KeyValueType = {}
export function getSetting (key: string) {
return publicSettings[key]
}
export function setSetting (setting: SettingType) {
if (setting.is_secret) {
secretSettings[setting.key] = setting.value
} else {
publicSettings[setting.key] = setting.value
}
return Setting.upsert(setting)
}
export async function loadSettings () {
const settings = await Setting.findAll()
for (const iterator of settings) {
publicSettings[iterator.key] = iterator.value
}
}