feat: découverte automatique des dépôts git (scan + montrer/cacher)
Scan borné du système de fichiers (racines configurables, défaut home ; profondeur/nombre/timeout bornés ; symlinks non suivis ; exclusions node_modules/dotdirs) qui auto-enregistre les nouveaux dépôts. Insertion atomique ON CONFLICT DO NOTHING (idempotence + anti-résurrection d'un dépôt masqué + anti-course). Scan au démarrage + bouton manuel + re-scan périodique (RepoDiscoveryService, démarré dans runDaemon). Colonne repos.hidden : masquer = conservé en DB mais exclu du dashboard et jamais ré-ajouté ; supprimer = re-découvrable. UI : bouton œil par dépôt + bascule afficher-les-masqués sur le dashboard, section Découverte dans les Réglages (racines + intervalle, allow-list stricte). Robustesse : listAllWorktrees tolère l'échec git par dépôt ; flag --no-discover (escape hatch + hermétisme des acceptations).
This commit is contained in:
@@ -6,8 +6,16 @@ import type { ServerInfo, SettingsResponse, UpdateSettingsRequest } from '@arbor
|
||||
import type { Config } from '../config.js';
|
||||
import { type Db, getSetting, setSetting } from '../db/index.js';
|
||||
import type { PushService } from '../core/push-service.js';
|
||||
import {
|
||||
SCAN_INTERVAL_KEY,
|
||||
SCAN_ROOTS_KEY,
|
||||
normalizeScanIntervalMin,
|
||||
normalizeScanRoots,
|
||||
readScanIntervalMin,
|
||||
readScanRoots,
|
||||
} from '../core/scan-settings.js';
|
||||
|
||||
// Seule clé de `settings` modifiable via l'API. Les secrets ne figurent JAMAIS ici.
|
||||
// Clés de `settings` modifiables via l'API (allow-list stricte). Les secrets ne figurent JAMAIS ici.
|
||||
const GITEA_URL_KEY = 'gitea_url';
|
||||
|
||||
/** Valide/normalise une URL Gitea : http(s) uniquement (anti-XSS sur le href de l'icône). */
|
||||
@@ -42,7 +50,14 @@ export function registerSettingsRoutes(
|
||||
vapidPublicKey: push.publicKey() || null,
|
||||
vapidContact: config.vapidContact,
|
||||
});
|
||||
const snapshot = (): SettingsResponse => ({ settings: { giteaUrl: readGiteaUrl() }, server: serverInfo() });
|
||||
const snapshot = (): SettingsResponse => ({
|
||||
settings: {
|
||||
giteaUrl: readGiteaUrl(),
|
||||
scanRoots: readScanRoots(db),
|
||||
scanIntervalMin: readScanIntervalMin(db),
|
||||
},
|
||||
server: serverInfo(),
|
||||
});
|
||||
|
||||
app.get('/api/v1/settings', async (): Promise<SettingsResponse> => snapshot());
|
||||
|
||||
@@ -62,6 +77,20 @@ export function registerSettingsRoutes(
|
||||
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'giteaUrl must be a string or null' } });
|
||||
}
|
||||
}
|
||||
if ('scanRoots' in body) {
|
||||
const roots = normalizeScanRoots(body.scanRoots);
|
||||
if (!roots) {
|
||||
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'scanRoots must be an array of ≤16 absolute, normalized paths (never "/")' } });
|
||||
}
|
||||
setSetting(db, SCAN_ROOTS_KEY, JSON.stringify(roots));
|
||||
}
|
||||
if ('scanIntervalMin' in body) {
|
||||
const interval = normalizeScanIntervalMin(body.scanIntervalMin);
|
||||
if (interval === null) {
|
||||
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'scanIntervalMin must be an integer between 0 and 1440' } });
|
||||
}
|
||||
setSetting(db, SCAN_INTERVAL_KEY, String(interval));
|
||||
}
|
||||
return reply.send(snapshot());
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user