100 lines
3.3 KiB
JavaScript
100 lines
3.3 KiB
JavaScript
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
import { randomBytes } from 'node:crypto';
|
|
import path from 'node:path';
|
|
|
|
const dataDirectory = path.join(process.cwd(), 'data');
|
|
const configPath = path.join(dataDirectory, 'config.json');
|
|
|
|
const defaultConfig = () => ({
|
|
adminToken: randomBytes(24).toString('base64url'),
|
|
settings: {
|
|
corsOrigins: [],
|
|
trustedProxyHops: 0
|
|
},
|
|
credentials: [],
|
|
documents: [],
|
|
publications: []
|
|
});
|
|
|
|
function migrateConfig(config) {
|
|
if (config.credentials) {
|
|
return {
|
|
...config,
|
|
settings: {
|
|
...config.settings,
|
|
corsOrigins: config.settings?.corsOrigins || [],
|
|
trustedProxyHops: Number(config.settings?.trustedProxyHops) || 0
|
|
}
|
|
};
|
|
}
|
|
const legacyCredential = config.google?.apiKey || config.google?.serviceAccountJson ? {
|
|
id: crypto.randomUUID(),
|
|
name: 'Imported Google connection',
|
|
mode: config.google.mode,
|
|
apiKey: config.google.apiKey,
|
|
serviceAccountJson: config.google.serviceAccountJson,
|
|
requestsPerMinute: config.google.requestsPerMinute || 45
|
|
} : null;
|
|
const documents = (config.publications || []).map((publication) => ({
|
|
id: crypto.randomUUID(),
|
|
name: publication.name,
|
|
spreadsheetId: publication.spreadsheetId,
|
|
credentialId: legacyCredential?.id || '',
|
|
sheets: publication.sheets || []
|
|
}));
|
|
return {
|
|
adminToken: config.adminToken,
|
|
settings: { corsOrigins: [], trustedProxyHops: 0 },
|
|
credentials: legacyCredential ? [legacyCredential] : [],
|
|
documents,
|
|
publications: (config.publications || []).map((publication, index) => ({
|
|
id: publication.id,
|
|
name: publication.name,
|
|
documentId: documents[index].id,
|
|
sheets: publication.sheets || [],
|
|
apiToken: publication.apiToken,
|
|
cacheSeconds: config.google?.cacheSeconds || 30
|
|
}))
|
|
};
|
|
}
|
|
|
|
export async function loadConfig() {
|
|
try {
|
|
const config = migrateConfig(JSON.parse(await readFile(configPath, 'utf8')));
|
|
await saveConfig(config);
|
|
return config;
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') throw error;
|
|
const config = defaultConfig();
|
|
await saveConfig(config);
|
|
console.log(`Created admin token: ${config.adminToken}`);
|
|
return config;
|
|
}
|
|
}
|
|
|
|
export async function saveConfig(config) {
|
|
await mkdir(dataDirectory, { recursive: true });
|
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
}
|
|
|
|
export function publicConfig(config) {
|
|
return {
|
|
settings: config.settings,
|
|
credentials: config.credentials.map(({ apiKey, serviceAccountJson, ...credential }) => ({
|
|
...credential,
|
|
configured: credential.mode === 'apiKey' ? Boolean(apiKey) : Boolean(serviceAccountJson)
|
|
})),
|
|
documents: config.documents,
|
|
publications: config.publications.map(({ apiToken, ...publication }) => ({
|
|
...publication,
|
|
dataFormat: publication.dataFormat || 'rows',
|
|
range: publication.range || '',
|
|
omitBlankRows: Boolean(publication.omitBlankRows),
|
|
maxRows: Math.max(1, Math.min(100_000, Number(publication.maxRows) || 10_000)),
|
|
consumerRequestsPerMinute: Math.max(1, Number(publication.consumerRequestsPerMinute) || 120),
|
|
scheduledRefresh: Boolean(publication.scheduledRefresh),
|
|
enabled: publication.enabled !== false,
|
|
tokenConfigured: Boolean(apiToken)
|
|
}))
|
|
};
|
|
} |