Formatting code

This commit is contained in:
Wayne
2025-08-29 15:20:02 +03:00
parent 535fde1426
commit 739c437b0a
16 changed files with 1447 additions and 1514 deletions

View File

@@ -75,9 +75,8 @@ export default defineConfig({
{ text: 'Overview', link: '/services/' },
{ text: 'Storage Service', link: '/services/storage-service' },
{
text: 'IAM Service', items: [
{ text: 'IAM Policies', link: '/services/iam-service/iam-policy' }
]
text: 'IAM Service',
items: [{ text: 'IAM Policies', link: '/services/iam-service/iam-policy' }],
},
],
},

View File

@@ -38,8 +38,7 @@ export class IngestionController {
logger.error({ err: error }, 'Create ingestion source error');
// Return a 400 Bad Request for connection errors
return res.status(400).json({
message:
error.message || req.t('ingestion.failedToCreate'),
message: error.message || req.t('ingestion.failedToCreate'),
});
}
};

View File

@@ -4,22 +4,22 @@ import { SettingsService } from '../../services/SettingsService';
const settingsService = new SettingsService();
export const getSettings = async (req: Request, res: Response) => {
try {
const settings = await settingsService.getSettings();
res.status(200).json(settings);
} catch (error) {
// A more specific error could be logged here
res.status(500).json({ message: req.t('settings.failedToRetrieve') });
}
try {
const settings = await settingsService.getSettings();
res.status(200).json(settings);
} catch (error) {
// A more specific error could be logged here
res.status(500).json({ message: req.t('settings.failedToRetrieve') });
}
};
export const updateSettings = async (req: Request, res: Response) => {
try {
// Basic validation can be performed here if necessary
const updatedSettings = await settingsService.updateSettings(req.body);
res.status(200).json(updatedSettings);
} catch (error) {
// A more specific error could be logged here
res.status(500).json({ message: req.t('settings.failedToUpdate') });
}
try {
// Basic validation can be performed here if necessary
const updatedSettings = await settingsService.updateSettings(req.body);
res.status(200).json(updatedSettings);
} catch (error) {
// A more specific error could be logged here
res.status(500).json({ message: req.t('settings.failedToUpdate') });
}
};

View File

@@ -4,7 +4,7 @@ import * as path from 'path';
import { storage as storageConfig } from '../../config/storage';
export class StorageController {
constructor(private storageService: StorageService) { }
constructor(private storageService: StorageService) {}
public downloadFile = async (req: Request, res: Response): Promise<void> => {
const unsafePath = req.query.path as string;

View File

@@ -5,21 +5,25 @@ import { requirePermission } from '../middleware/requirePermission';
import { AuthService } from '../../services/AuthService';
export const createSettingsRouter = (authService: AuthService): Router => {
const router = Router();
const router = Router();
// Public route to get non-sensitive settings. settings read should not be scoped with a permission because all end users need the settings data in the frontend. However, for sensitive settings data, we need to add a new permission subject to limit access. So this route should only expose non-sensitive settings data.
/**
* @returns SystemSettings
*/
router.get('/', settingsController.getSettings);
// Public route to get non-sensitive settings. settings read should not be scoped with a permission because all end users need the settings data in the frontend. However, for sensitive settings data, we need to add a new permission subject to limit access. So this route should only expose non-sensitive settings data.
/**
* @returns SystemSettings
*/
router.get('/', settingsController.getSettings);
// Protected route to update settings
router.put(
'/',
requireAuth(authService),
requirePermission('manage', 'settings', 'You do not have permission to update system settings.'),
settingsController.updateSettings
);
// Protected route to update settings
router.put(
'/',
requireAuth(authService),
requirePermission(
'manage',
'settings',
'You do not have permission to update system settings.'
),
settingsController.updateSettings
);
return router;
return router;
};

View File

@@ -1,132 +1,132 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1752225352591,
"tag": "0000_amusing_namora",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1752326803882,
"tag": "0001_odd_night_thrasher",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1752332648392,
"tag": "0002_lethal_quentin_quire",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1752332967084,
"tag": "0003_petite_wrecker",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1752606108876,
"tag": "0004_sleepy_paper_doll",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1752606327253,
"tag": "0005_chunky_sue_storm",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1753112018514,
"tag": "0006_majestic_caretaker",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1753190159356,
"tag": "0007_handy_archangel",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1753370737317,
"tag": "0008_eminent_the_spike",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1754337938241,
"tag": "0009_late_lenny_balinger",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1754420780849,
"tag": "0010_perpetual_lightspeed",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1754422064158,
"tag": "0011_tan_blackheart",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1754476962901,
"tag": "0012_warm_the_stranger",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1754659373517,
"tag": "0013_classy_talkback",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1754831765718,
"tag": "0014_foamy_vapor",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1755443936046,
"tag": "0015_wakeful_norman_osborn",
"breakpoints": true
},
{
"idx": 16,
"version": "7",
"when": 1755780572342,
"tag": "0016_lonely_mariko_yashida",
"breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1755961566627,
"tag": "0017_tranquil_shooting_star",
"breakpoints": true
}
]
}
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1752225352591,
"tag": "0000_amusing_namora",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1752326803882,
"tag": "0001_odd_night_thrasher",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1752332648392,
"tag": "0002_lethal_quentin_quire",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1752332967084,
"tag": "0003_petite_wrecker",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1752606108876,
"tag": "0004_sleepy_paper_doll",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1752606327253,
"tag": "0005_chunky_sue_storm",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1753112018514,
"tag": "0006_majestic_caretaker",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1753190159356,
"tag": "0007_handy_archangel",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1753370737317,
"tag": "0008_eminent_the_spike",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1754337938241,
"tag": "0009_late_lenny_balinger",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1754420780849,
"tag": "0010_perpetual_lightspeed",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1754422064158,
"tag": "0011_tan_blackheart",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1754476962901,
"tag": "0012_warm_the_stranger",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1754659373517,
"tag": "0013_classy_talkback",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1754831765718,
"tag": "0014_foamy_vapor",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1755443936046,
"tag": "0015_wakeful_norman_osborn",
"breakpoints": true
},
{
"idx": 16,
"version": "7",
"when": 1755780572342,
"tag": "0016_lonely_mariko_yashida",
"breakpoints": true
},
{
"idx": 17,
"version": "7",
"when": 1755961566627,
"tag": "0017_tranquil_shooting_star",
"breakpoints": true
}
]
}

View File

@@ -5,4 +5,4 @@ export * from './schema/compliance';
export * from './schema/custodians';
export * from './schema/ingestion-sources';
export * from './schema/users';
export * from './schema/system-settings'
export * from './schema/system-settings';

View File

@@ -2,6 +2,6 @@ import { pgTable, serial, jsonb } from 'drizzle-orm/pg-core';
import type { SystemSettings } from '@open-archiver/types';
export const systemSettings = pgTable('system_settings', {
id: serial('id').primaryKey(),
config: jsonb('config').$type<SystemSettings>().notNull(),
id: serial('id').primaryKey(),
config: jsonb('config').$type<SystemSettings>().notNull(),
});

View File

@@ -61,17 +61,15 @@ const initializeI18next = async () => {
const systemSettings = await settingsService.getSettings();
const defaultLanguage = systemSettings?.language || 'en';
logger.info({ language: defaultLanguage }, 'Default language');
await i18next
.use(FsBackend)
.init({
lng: defaultLanguage,
fallbackLng: defaultLanguage,
ns: ['translation'],
defaultNS: 'translation',
backend: {
loadPath: path.resolve(__dirname, './locales/{{lng}}/{{ns}}.json'),
},
});
await i18next.use(FsBackend).init({
lng: defaultLanguage,
fallbackLng: defaultLanguage,
ns: ['translation'],
defaultNS: 'translation',
backend: {
loadPath: path.resolve(__dirname, './locales/{{lng}}/{{ns}}.json'),
},
});
};
// --- Express App Initialization ---

View File

@@ -4,57 +4,52 @@ import type { SystemSettings } from '@open-archiver/types';
import { eq } from 'drizzle-orm';
const DEFAULT_SETTINGS: SystemSettings = {
language: 'en',
theme: 'system',
supportEmail: null,
language: 'en',
theme: 'system',
supportEmail: null,
};
export class SettingsService {
/**
* Retrieves the current system settings.
* If no settings exist, it initializes and returns the default settings.
* @returns The system settings.
*/
public async getSettings(): Promise<SystemSettings> {
const settings = await db.select().from(systemSettings).limit(1);
/**
* Retrieves the current system settings.
* If no settings exist, it initializes and returns the default settings.
* @returns The system settings.
*/
public async getSettings(): Promise<SystemSettings> {
const settings = await db.select().from(systemSettings).limit(1);
if (settings.length === 0) {
return this.createDefaultSettings();
}
if (settings.length === 0) {
return this.createDefaultSettings();
}
return settings[0].config;
}
return settings[0].config;
}
/**
* Updates the system settings by merging the new configuration with the existing one.
* @param newConfig - A partial object of the new settings configuration.
* @returns The updated system settings.
*/
public async updateSettings(
newConfig: Partial<SystemSettings>
): Promise<SystemSettings> {
const currentConfig = await this.getSettings();
const mergedConfig = { ...currentConfig, ...newConfig };
/**
* Updates the system settings by merging the new configuration with the existing one.
* @param newConfig - A partial object of the new settings configuration.
* @returns The updated system settings.
*/
public async updateSettings(newConfig: Partial<SystemSettings>): Promise<SystemSettings> {
const currentConfig = await this.getSettings();
const mergedConfig = { ...currentConfig, ...newConfig };
// Since getSettings ensures a record always exists, we can directly update.
const [result] = await db
.update(systemSettings)
.set({ config: mergedConfig })
.returning();
// Since getSettings ensures a record always exists, we can directly update.
const [result] = await db.update(systemSettings).set({ config: mergedConfig }).returning();
return result.config;
}
return result.config;
}
/**
* Creates and saves the default system settings.
* This is called internally when no settings are found.
* @returns The newly created default settings.
*/
private async createDefaultSettings(): Promise<SystemSettings> {
const [result] = await db
.insert(systemSettings)
.values({ config: DEFAULT_SETTINGS })
.returning();
return result.config;
}
/**
* Creates and saves the default system settings.
* This is called internally when no settings are found.
* @returns The newly created default settings.
*/
private async createDefaultSettings(): Promise<SystemSettings> {
const [result] = await db
.insert(systemSettings)
.values({ config: DEFAULT_SETTINGS })
.returning();
return result.config;
}
}

View File

@@ -15,70 +15,70 @@ import el from './el.json';
// This is your config object.
// It defines the languages and how to load them.
const config: Config = {
// Define the loaders for each language
loaders: [
// English 🇬🇧
{
locale: 'en',
key: 'app', // This key matches the top-level key in your en.json
loader: async () => en.app, // We return the nested 'app' object
},
// German 🇩🇪
{
locale: 'de',
key: 'app', // This key matches the top-level key in your en.json
loader: async () => de.app, // We return the nested 'app' object
},
// Spanish 🇪🇸
{
locale: 'es',
key: 'app',
loader: async () => es.app,
},
// French 🇫🇷
{
locale: 'fr',
key: 'app',
loader: async () => fr.app,
},
// Italian 🇮🇹
{
locale: 'it',
key: 'app',
loader: async () => it.app,
},
// Portuguese 🇵🇹
{
locale: 'pt',
key: 'app',
loader: async () => pt.app,
},
// Dutch 🇳🇱
{
locale: 'nl',
key: 'app',
loader: async () => nl.app,
},
// Japanese 🇯🇵
{
locale: 'ja',
key: 'app',
loader: async () => ja.app,
},
// Estonian 🇪🇪
{
locale: 'et',
key: 'app',
loader: async () => et.app,
},
// Greek 🇬🇷
{
locale: 'el',
key: 'app',
loader: async () => el.app,
},
],
fallbackLocale: 'en',
// Define the loaders for each language
loaders: [
// English 🇬🇧
{
locale: 'en',
key: 'app', // This key matches the top-level key in your en.json
loader: async () => en.app, // We return the nested 'app' object
},
// German 🇩🇪
{
locale: 'de',
key: 'app', // This key matches the top-level key in your en.json
loader: async () => de.app, // We return the nested 'app' object
},
// Spanish 🇪🇸
{
locale: 'es',
key: 'app',
loader: async () => es.app,
},
// French 🇫🇷
{
locale: 'fr',
key: 'app',
loader: async () => fr.app,
},
// Italian 🇮🇹
{
locale: 'it',
key: 'app',
loader: async () => it.app,
},
// Portuguese 🇵🇹
{
locale: 'pt',
key: 'app',
loader: async () => pt.app,
},
// Dutch 🇳🇱
{
locale: 'nl',
key: 'app',
loader: async () => nl.app,
},
// Japanese 🇯🇵
{
locale: 'ja',
key: 'app',
loader: async () => ja.app,
},
// Estonian 🇪🇪
{
locale: 'et',
key: 'app',
loader: async () => et.app,
},
// Greek 🇬🇷
{
locale: 'el',
key: 'app',
loader: async () => el.app,
},
],
fallbackLocale: 'en',
};
// Create the i18n instance.
@@ -89,7 +89,6 @@ export const { t, locale, locales, loading, loadTranslations } = new i18n(config
// Export the t store for use in components
// export const t = i18n.t;
// import i18n from 'sveltekit-i18n';
// import type { Config } from 'sveltekit-i18n';

View File

@@ -22,7 +22,9 @@ export const load: LayoutServerLoad = async (event) => {
}
const settingsResponse = await api('/settings', event);
const settings: SystemSettings | null = settingsResponse.ok ? await settingsResponse.json() : null;
const settings: SystemSettings | null = settingsResponse.ok
? await settingsResponse.json()
: null;
return {
user: locals.user,

View File

@@ -4,18 +4,18 @@ import { browser } from '$app/environment';
import type { SupportedLanguage } from '@open-archiver/types';
export const load: LayoutLoad = async ({ url, data }) => {
const { pathname } = url;
const { pathname } = url;
let initLocale: SupportedLanguage = 'en'; // Default fallback
let initLocale: SupportedLanguage = 'en'; // Default fallback
if (data.settings?.language) {
initLocale = data.settings.language;
}
if (data.settings?.language) {
initLocale = data.settings.language;
}
console.log(initLocale);
await loadTranslations(initLocale, pathname);
console.log(initLocale);
await loadTranslations(initLocale, pathname);
return {
...data
};
return {
...data,
};
};

View File

@@ -4,47 +4,47 @@ import { error, fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async (event) => {
const response = await api('/settings', event);
const response = await api('/settings', event);
if (!response.ok) {
const { message } = await response.json();
throw error(response.status, message || 'Failed to fetch system settings');
}
if (!response.ok) {
const { message } = await response.json();
throw error(response.status, message || 'Failed to fetch system settings');
}
const settings: SystemSettings = await response.json();
return {
settings,
};
const settings: SystemSettings = await response.json();
return {
settings,
};
};
export const actions: Actions = {
default: async (event) => {
const formData = await event.request.formData();
const language = formData.get('language');
const theme = formData.get('theme');
const supportEmail = formData.get('supportEmail');
default: async (event) => {
const formData = await event.request.formData();
const language = formData.get('language');
const theme = formData.get('theme');
const supportEmail = formData.get('supportEmail');
const body: Partial<SystemSettings> = {
language: language as SystemSettings['language'],
theme: theme as SystemSettings['theme'],
supportEmail: supportEmail ? String(supportEmail) : null,
};
const body: Partial<SystemSettings> = {
language: language as SystemSettings['language'],
theme: theme as SystemSettings['theme'],
supportEmail: supportEmail ? String(supportEmail) : null,
};
const response = await api('/settings', event, {
method: 'PUT',
body: JSON.stringify(body),
});
const response = await api('/settings', event, {
method: 'PUT',
body: JSON.stringify(body),
});
if (!response.ok) {
const { message } = await response.json();
return fail(response.status, { message: message || 'Failed to update settings' });
}
if (!response.ok) {
const { message } = await response.json();
return fail(response.status, { message: message || 'Failed to update settings' });
}
const updatedSettings: SystemSettings = await response.json();
const updatedSettings: SystemSettings = await response.json();
return {
success: true,
settings: updatedSettings,
};
},
return {
success: true,
settings: updatedSettings,
};
},
};

View File

@@ -1,24 +1,24 @@
export type SupportedLanguage =
| 'en' // English
| 'es' // Spanish
| 'fr' // French
| 'de' // German
| 'it' // Italian
| 'pt' // Portuguese
| 'nl' // Dutch
| 'ja' // Japanese
| 'et' // Estonian
| 'el'; // Greek
| 'en' // English
| 'es' // Spanish
| 'fr' // French
| 'de' // German
| 'it' // Italian
| 'pt' // Portuguese
| 'nl' // Dutch
| 'ja' // Japanese
| 'et' // Estonian
| 'el'; // Greek
export type Theme = 'light' | 'dark' | 'system';
export interface SystemSettings {
/** The default display language for the application UI. */
language: SupportedLanguage;
/** The default display language for the application UI. */
language: SupportedLanguage;
/** The default color theme for the application. */
theme: Theme;
/** The default color theme for the application. */
theme: Theme;
/** A public-facing email address for user support inquiries. */
supportEmail: string | null;
/** A public-facing email address for user support inquiries. */
supportEmail: string | null;
}