-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ✨ gestion de la config console (System Settings)
- Loading branch information
1 parent
ac7d60a
commit a28d3f7
Showing
12 changed files
with
258 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,29 @@ | ||
import { defineStore } from 'pinia' | ||
import { | ||
type SystemSetting, | ||
type SystemSettings, | ||
type UpsertSystemSettingBody, | ||
resourceListToDictByKey, | ||
import type { | ||
SystemSettings, | ||
} from '@cpn-console/shared' | ||
import { apiClient, extractData } from '@/api/xhr-client.js' | ||
|
||
export const useSystemSettingsStore = defineStore('systemSettings', () => { | ||
const systemSettings = ref<SystemSettings>([]) | ||
const systemSettingsByKey = computed(() => resourceListToDictByKey(systemSettings.value)) | ||
const systemSettings = ref<SystemSettings>() | ||
|
||
const listSystemSettings = async (key?: SystemSetting['key']) => { | ||
const listSystemSettings = async (key?: keyof SystemSettings) => { | ||
systemSettings.value = await apiClient.SystemSettings.listSystemSettings({ query: { key } }) | ||
.then(response => extractData(response, 200)) | ||
} | ||
|
||
const upsertSystemSetting = async (newSystemSetting: UpsertSystemSettingBody) => { | ||
const res = await apiClient.SystemSettings.upsertSystemSetting({ body: newSystemSetting }) | ||
.then(response => extractData(response, 201)) | ||
systemSettings.value = systemSettings.value | ||
.toSpliced(systemSettings.value | ||
.findIndex(systemSetting => systemSetting.key === res.key), 1, res) | ||
return res | ||
} | ||
// const upsertSystemSetting = async (newSystemSetting: UpsertSystemSettingBody) => { | ||
// const res = await apiClient.SystemSettings.upsertSystemSetting({ body: newSystemSetting }) | ||
// .then(response => extractData(response, 201)) | ||
// systemSettings.value = systemSettings.value | ||
// .toSpliced(systemSettings.value | ||
// .findIndex(systemSetting => systemSetting.key === res.key), 1, res) | ||
// return res | ||
// } | ||
|
||
return { | ||
systemSettings, | ||
systemSettingsByKey, | ||
listSystemSettings, | ||
upsertSystemSetting, | ||
// upsertSystemSetting, | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,85 @@ | ||
<script lang="ts" setup> | ||
import type { SystemSettings } from '@cpn-console/shared' | ||
import { useSystemSettingsStore } from '@/stores/system-settings.js' | ||
const systemStore = useSystemSettingsStore() | ||
onBeforeMount(async () => { | ||
const updated = ref<Record<string, Record<string, string>>>({}) | ||
// permet de definir quel input choisir ?? | ||
// function refTheValues(settings: SystemSetting[]) { | ||
// return settings.map((setting) => { | ||
// return { | ||
// ...setting, | ||
// // manifest ?? | ||
// } | ||
// }) | ||
// } | ||
const systemSettings = ref<SystemSettings>() | ||
// reload les settings | ||
async function reload() { | ||
await systemStore.listSystemSettings() | ||
systemSettings.value = systemStore.systemSettings | ||
updated.value = {} | ||
} | ||
onBeforeMount(async () => { | ||
// await systemStore.listSystemSettings() | ||
reload() | ||
}) | ||
// A modifié pour save les settings dynamiquement | ||
async function upsertSystemSetting(key: string, value: boolean) { | ||
await systemStore.upsertSystemSetting({ key, value: value ? 'on' : 'off' }) | ||
// await systemStore.upsertSystemSetting({ key, value: value ? 'on' : 'off' }) | ||
console.log(key + value) | ||
} | ||
</script> | ||
|
||
<template> | ||
<h1>Réglages de la console Cloud π Native</h1> | ||
<div | ||
class="flex <md:flex-col-reverse items-center justify-between gap-2 mt-8" | ||
class="flex <md:flex-col items-center justify-between gap-2 mt-8" | ||
> | ||
<!-- {{ systemSettings }} --> | ||
<DsfrToggleSwitch | ||
v-for="setting in systemStore.systemSettings" | ||
:key="setting.key" | ||
:model-value="setting.value === 'on'" | ||
:label="`${setting.value === 'on' ? 'Désactiver' : 'Activer'} le mode ${setting.key}`" | ||
:data-testid="`toggle-${setting.key}`" | ||
@update:model-value="(event: boolean) => upsertSystemSetting(setting.key, event)" | ||
:model-value="systemSettings.maintenance === 'on'" | ||
:label="`${systemSettings.maintenance === 'on' ? 'Désactiver' : 'Activer'} le mode maintenance`" | ||
data-testid="toggle-maintenance" | ||
@update:model-value="(event: boolean) => upsertSystemSetting('maintenance', event)" | ||
/> | ||
<DsfrInput | ||
v-model="systemSettings.appName" | ||
data-testid="input-appName" | ||
label="appName" | ||
label-visible | ||
/> | ||
<DsfrInput | ||
v-model="systemSettings.contactMail" | ||
data-testid="input-contactMail" | ||
label="contactMail" | ||
label-visible | ||
/> | ||
<DsfrInput | ||
v-model="systemSettings.appSubTitle" | ||
data-testid="input-appSubTitle" | ||
label="appSubTitle" | ||
label-visible | ||
/> | ||
</div> | ||
</template> | ||
|
||
<!-- <template> | ||
<h1>Réglages de la console Cloud π Native</h1> | ||
<div | ||
v-if="!systemStore.systemSettings.length" | ||
class="flex <md:flex-col-reverse items-center justify-between gap-2 mt-8" | ||
> | ||
<div> --> | ||
<!-- trouvé comment faire du l'input dynamique --> | ||
<!-- HINT : etablir des regle, ex : on|off => switch, sinon input classic --> | ||
<!-- </div> | ||
</div> | ||
</template> --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
import type { UpsertSystemSettingBody } from '@cpn-console/shared' | ||
import { | ||
getSystemSettings as getSystemSettingsQuery, | ||
upsertSystemSetting as upsertSystemSettingQuery, | ||
} from './queries.js' | ||
import type { UpsertSystemSettingsBody } from '@cpn-console/shared' | ||
import { upsertSystemSetting as upsertSystemSettingQuery } from './queries.js' | ||
|
||
export const getSystemSettings = (key?: string) => getSystemSettingsQuery({ key }) | ||
import { config } from '@/utils/config.js' | ||
|
||
export const upsertSystemSetting = (newSystemSetting: UpsertSystemSettingBody) => upsertSystemSettingQuery(newSystemSetting) | ||
export function getSystemSettings(key?: keyof typeof config) { | ||
if (key) { | ||
return { [key]: config[key] } | ||
} else { | ||
return config | ||
} | ||
} | ||
|
||
export const upsertSystemSettings = (newSystemSetting: UpsertSystemSettingsBody) => upsertSystemSettingQuery(newSystemSetting) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import path from 'node:path' | ||
import { SystemSettingSchema as ConfigSchema } from '@cpn-console/shared' | ||
|
||
const getNodeEnv: () => 'development' | 'test' | 'production' = () => { | ||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { | ||
return process.env.NODE_ENV | ||
} | ||
return 'production' | ||
} | ||
|
||
function snakeCaseToCamelCase(input: string) { | ||
return input | ||
.split('_') | ||
.reduce((acc, cur, i) => { | ||
if (!i) { | ||
return cur.toLowerCase() | ||
} | ||
return acc + cur.charAt(0).toUpperCase() + cur.substring(1).toLowerCase() | ||
}, '') | ||
} | ||
|
||
function deepMerge(target: any, source: any) { | ||
const result = { ...target, ...source } | ||
for (const key of Object.keys(result)) { | ||
if (Array.isArray(target[key]) && Array.isArray(source[key])) { | ||
result[key] = result[key].map((value: unknown, idx: number) => { | ||
return typeof value === 'object' | ||
? deepMerge(target[key][idx], source[key][idx]) | ||
: structuredClone(result[key][idx]) | ||
}) | ||
} else if (typeof target[key] === 'object' && typeof source[key] === 'object') { | ||
result[key] = deepMerge(target[key], source[key]) | ||
} else { | ||
result[key] = structuredClone(result[key]) | ||
} | ||
} | ||
return result | ||
} | ||
|
||
const configPaths = { | ||
development: path.resolve(__dirname, '../../config-example.json'), | ||
production: '/app/config.json', | ||
test: path.resolve(__dirname, './configs/config.valid.spec.json'), | ||
} | ||
|
||
const CONFIG_PATH = configPaths[getNodeEnv()] | ||
const ENV_PREFIX = ['API__', 'DOC__'] | ||
|
||
// export const ConfigSchema = z.object({ | ||
// maintenance: z.string().default('off'), | ||
// appName: z.string().default('Console Cloud Pi Native TEST DE FOU'), | ||
// contactMail: z.string().default('[email protected]'), | ||
// appSubTitle: z.array(z.string()).default(['Ministère 2', 'de l’intérieur 3', 'et des outre-mer 4']), | ||
// // appLogoUrl: z.string().default(''), // pas sur de la faisabilité | ||
// }).strict() | ||
|
||
export type Config = Zod.infer<typeof ConfigSchema> | ||
|
||
// maybe a modifié ? ? | ||
export function parseEnv(obj: Record<string, string>): Config | Record<PropertyKey, never> { | ||
return Object | ||
.entries(obj) | ||
.map(([key, value]) => key | ||
.split('__') | ||
.toReversed() | ||
.reduce((acc, val, idx) => { | ||
if (!idx) { | ||
try { | ||
return { [snakeCaseToCamelCase(val)]: JSON.parse(value) } | ||
} catch (_e) { | ||
return { [snakeCaseToCamelCase(val)]: value } | ||
} | ||
} else { | ||
return { [snakeCaseToCamelCase(val)]: acc } | ||
} | ||
}, {})) | ||
.reduce((acc, val) => deepMerge(acc, val), {}) | ||
} | ||
|
||
// pour recup l'env | ||
export function getEnv(prefix: string | string[] = ENV_PREFIX): Record<string, string> { | ||
return Object | ||
.entries(process.env) | ||
.filter(([key, _value]) => Array.isArray(prefix) ? prefix.some(p => key.startsWith(p)) : key.startsWith(prefix)) | ||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) | ||
} | ||
|
||
export async function getConfig(opts?: { fileConfigPath?: string, envPrefix?: string | string[] }) { | ||
const fileConfigPath = opts?.fileConfigPath ?? CONFIG_PATH | ||
const envPrefix = opts?.envPrefix ?? ENV_PREFIX | ||
|
||
const defaultConfig = ConfigSchema.parse({}) | ||
let envConfig: Config | Record<PropertyKey, never> = {} | ||
let fileConfig: Config | Record<PropertyKey, never> = {} | ||
// const dbConfig: Config | Record<PropertyKey, never> = {} | ||
|
||
try { | ||
envConfig = parseEnv(getEnv(envPrefix)) | ||
ConfigSchema.partial().parse(envConfig) | ||
} catch (error) { | ||
const errorMessage = { description: 'invalid config environment variables', error } | ||
throw new Error(JSON.stringify(errorMessage)) | ||
} | ||
|
||
try { | ||
const file = await import(fileConfigPath, { assert: { type: 'json' } }) | ||
|
||
.catch(_e => console.log(`no config file detected "${fileConfigPath}"`)) | ||
if (file) { | ||
fileConfig = file.default | ||
ConfigSchema.partial().parse(fileConfig) | ||
} | ||
} catch (error) { | ||
const errorMessage = { description: `invalid config file "${fileConfigPath}"`, error } | ||
throw new Error(JSON.stringify(errorMessage)) | ||
} | ||
|
||
// try { | ||
// dbConfig = JSON.parse(await getSystemSettings()) | ||
// } catch (error) { | ||
// const errorMessage = { description: `invalid config env`, error } | ||
// throw new Error(JSON.stringify(errorMessage)) | ||
// } | ||
|
||
return { | ||
...defaultConfig, | ||
...fileConfig, | ||
...envConfig, | ||
// ...dbConfig, | ||
} | ||
} | ||
|
||
export const config = await getConfig() |
Oops, something went wrong.