mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 16:42:22 +01:00
settings, admin panel
This commit is contained in:
parent
a4fafc180e
commit
d0745115c7
13 changed files with 209 additions and 40 deletions
|
@ -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>
|
|
@ -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/> -->
|
||||
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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'
|
||||
|
|
17
package.json
17
package.json
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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
82
pages/tag/[tag].vue
Normal 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>
|
||||
-->
|
10
server/api/settings/index.get.ts
Normal file
10
server/api/settings/index.get.ts
Normal 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
|
||||
})
|
|
@ -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)
|
||||
})
|
|
@ -1,3 +0,0 @@
|
|||
export default defineEventHandler( event => {
|
||||
return Setting.findAll({ where: { is_secret: false }})
|
||||
})
|
|
@ -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
37
server/utils/settings.ts
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue