Control plane wiring (plan §5-§11): - PageService create/update/movePage now honor provenance actor 'git-sync' (stamp lastUpdatedSource='git-sync'), closing the A.4a gap. - EnvironmentService: GIT_SYNC_ENABLED / DATA_DIR / REMOTE_TEMPLATE / POLL_INTERVAL_MS / DEBOUNCE_MS / SERVICE_USER_ID (required-if-enabled) / SSH_KEY_PATH + validation. - VaultRegistryService: per-space vault path + cached VaultGit. - GitSyncOrchestrator: per-space Redis leader-lock (SET NX PX + CAS-Lua release, randomUUID instanceId) + in-process mutex; runOnce drives the vendored engine PULL (readExisting->computePullActions->applyPullActions) then PUSH (runPush) with the bound native GitSyncClient + VaultGit; @Interval poll-safety gated on GIT_SYNC_ENABLED; imports plain ScheduleModule (TelemetryModule owns forRoot). - PageChangeListener: @OnEvent PAGE_* -> per-space debounce -> runOnce, with a best-effort lastUpdatedSource==='git-sync' loop-guard. - GitSyncController: admin POST /api/git-sync/trigger + GET /status (ops/e2e). - GitSyncModule registered in app.module. Enabled-space enumeration uses settings.gitSync.enabled, falling back to all live spaces until Phase C writes the flag (master gate = GIT_SYNC_ENABLED). tsc clean; 713 tests/71 suites pass; dev server hot-reloaded the module (route live, DI graph boots). Live pull/push round-trip verified next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
1.8 KiB
TypeScript
45 lines
1.8 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { mkdir } from 'node:fs/promises';
|
|
import { VaultGit } from '@docmost/git-sync';
|
|
import { EnvironmentService } from '../../environment/environment.service';
|
|
|
|
/**
|
|
* Resolves the on-disk vault location per space and owns the (lazily created,
|
|
* cached) `VaultGit` instance for each one (plan §3/§5).
|
|
*
|
|
* Topology (plan §5): one git repo per enabled space, rooted at
|
|
* `<GIT_SYNC_DATA_DIR>/<spaceId>`. A `VaultGit` is constructed at most once per
|
|
* space and reused across cycles — it is a thin, stateless shell-out wrapper, so
|
|
* caching it just avoids re-resolving the path and re-running `mkdir`.
|
|
*/
|
|
@Injectable()
|
|
export class VaultRegistryService {
|
|
private readonly logger = new Logger(VaultRegistryService.name);
|
|
private readonly vaults = new Map<string, VaultGit>();
|
|
|
|
constructor(private readonly environmentService: EnvironmentService) {}
|
|
|
|
/** Absolute vault path for a space: `<GIT_SYNC_DATA_DIR>/<spaceId>`. */
|
|
vaultPath(spaceId: string): string {
|
|
const root = this.environmentService.getGitSyncDataDir().replace(/\/+$/, '');
|
|
return `${root}/${spaceId}`;
|
|
}
|
|
|
|
/**
|
|
* Get (or lazily construct + cache) the `VaultGit` for a space, ensuring its
|
|
* directory exists. `VaultGit.ensureRepo()` is NOT called here — the engine's
|
|
* pull/push paths call it (and the branch/ref setup) as their first step; this
|
|
* only guarantees the parent dir exists so a fresh space does not ENOENT.
|
|
*/
|
|
async getVault(spaceId: string): Promise<VaultGit> {
|
|
const cached = this.vaults.get(spaceId);
|
|
if (cached) return cached;
|
|
|
|
const path = this.vaultPath(spaceId);
|
|
await mkdir(path, { recursive: true });
|
|
const vault = new VaultGit(path);
|
|
this.vaults.set(spaceId, vault);
|
|
return vault;
|
|
}
|
|
}
|