feat: add WebDAV and PostgREST journal remotes

This commit is contained in:
vorotamoroz
2026-07-31 03:21:36 +00:00
parent b21c3114e5
commit 5acad15eba
30 changed files with 1317 additions and 237 deletions
+9 -5
View File
@@ -8,7 +8,7 @@ import {
import FetchEverything from "@/modules/features/SetupWizard/dialogs/FetchEverything.svelte";
import RebuildEverything from "@/modules/features/SetupWizard/dialogs/RebuildEverything.svelte";
import { extractObject } from "octagonal-wheels/object";
import { REMOTE_MINIO, REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
import { REMOTE_P2P } from "@vrtmrz/livesync-commonlib/compat/common/models/setting.const";
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/settings";
import { TweakValuesShouldMatchedTemplate } from "@vrtmrz/livesync-commonlib/compat/common/models/tweak.definition";
import type {
@@ -17,7 +17,11 @@ import type {
} from "@/modules/features/SetupWizard/dialogs/setupDialogTypes";
import { askAndPerformFastSetupOnScheduledFetchAll } from "./redFlag.simpleFetch";
import { ConnectionStringParser } from "@vrtmrz/livesync-commonlib/compat/common/ConnectionString";
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/remote-configurations";
import {
activateRemoteConfiguration,
suggestRemoteConfigurationName,
} from "@vrtmrz/livesync-commonlib/remote-configurations";
import { isJournalRemoteType } from "@vrtmrz/livesync-commonlib/journal-storage";
import { isP2PMainRemote } from "@/common/remoteConfiguration";
/**
@@ -61,7 +65,7 @@ async function askAndActivateRemoteDatabase(host: NecessaryServices<"UI" | "sett
"Multiple remote configurations detected. Please select the remote configuration you want to fetch from.";
const options = Object.entries(settings.remoteConfigurations).map(([id, config]) => {
const parsed = ConnectionStringParser.parse(config.uri);
const displayURI = (config.uri.split("@").pop() || "").substring(0, 20) + "..."; // Show only the last part of URI for better readability and privacy.
const displayURI = suggestRemoteConfigurationName(parsed);
return {
name: `${config.name} - ${parsed.type} (${displayURI})`,
id: id,
@@ -164,8 +168,8 @@ export function createFetchAllFlagHandler(
}
const { vault, extra } = method;
const settings = await Promise.resolve(host.services.setting.currentSettings());
// If remote is MinIO, makeLocalChunkBeforeSync is not available. (because no-deduplication on sending).
const makeLocalChunkBeforeSyncAvailable = settings.remoteType !== REMOTE_MINIO;
// Journal Storage remotes do not deduplicate chunks while sending.
const makeLocalChunkBeforeSyncAvailable = !isJournalRemoteType(settings.remoteType);
const mapVaultStateToAction = {
identical: {
makeLocalChunkBeforeSync: makeLocalChunkBeforeSyncAvailable,
+73 -13
View File
@@ -41,6 +41,11 @@ import {
askSimpleFetchMode,
} from "./redFlag.simpleFetch";
import { activateRemoteConfiguration } from "@vrtmrz/livesync-commonlib/remote-configurations";
import {
REMOTE_POSTGREST,
REMOTE_WEBDAV,
serialiseWebDAVConnectionURI,
} from "@vrtmrz/livesync-commonlib/journal-storage";
//Mock synchroniseAllFilesBetweenDBandStorage
vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner", async (importOriginal) => {
const originalModule = (await importOriginal()) as any;
@@ -50,8 +55,10 @@ vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner", asyn
};
});
vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig", () => {
vi.mock("@vrtmrz/livesync-commonlib/compat/serviceFeatures/remoteConfig", async (importOriginal) => {
const originalModule = (await importOriginal()) as any;
return {
...originalModule,
activateRemoteConfiguration: vi.fn((settings: any, configurationId: string) => {
if (!settings?.remoteConfigurations?.[configurationId]) return false;
return {
@@ -579,6 +586,40 @@ describe("Red Flag Feature", () => {
expect(host.mocks.ui.confirm.confirmWithMessage).not.toHaveBeenCalled();
});
it("does not expose unauthenticated WebDAV headers in the remote selection", async () => {
const host = createHostMock();
const log = createLoggerMock();
const privateHeader = "x-private-header: private-value";
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
Object.assign(host.mocks.setting.settings, {
remoteConfigurations: {
alpha: {
name: "Alpha",
uri: serialiseWebDAVConnectionURI({
endpoint: "https://dav.example/alpha",
username: "",
password: "",
prefix: "",
useCustomRequestHandler: false,
customHeaders: privateHeader,
}),
},
beta: {
name: "Beta",
uri: "sls+https://user:pass@example.com/db2",
},
},
});
host.mocks.ui.confirm.askSelectStringDialogue.mockResolvedValueOnce("Cancel");
await createFetchAllFlagHandler(host as any, log).handle();
const selections = host.mocks.ui.confirm.askSelectStringDialogue.mock.calls[0]?.[1] as string[];
expect(selections.join("\n")).not.toContain("private-value");
expect(selections).toContain("Alpha - webdav (WebDAV dav.example)");
});
it("should activate selected remote configuration", async () => {
const host = createHostMock();
const log = createLoggerMock();
@@ -1140,25 +1181,44 @@ describe("Red Flag Feature", () => {
});
});
describe("MinIO configuration handling", () => {
it("should not enable makeLocalChunkBeforeSync when remote is MinIO", () => {
const host = createHostMock();
host.mocks.setting.settings.remoteType = REMOTE_MINIO;
describe("Journal Storage configuration handling", () => {
it.each([REMOTE_MINIO, REMOTE_WEBDAV, REMOTE_POSTGREST])(
"does not prepare deduplicated local chunks for %s",
async (remoteType) => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.setting.settings.remoteType = remoteType;
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_DETAILED);
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({
vault: "identical",
backup: "backup_skipped",
extra: { preventFetchingConfig: true },
});
const settings = host.mocks.setting.currentSettings();
const isMinIO = settings.remoteType === REMOTE_MINIO;
const result = await createFetchAllFlagHandler(host as any, log).handle();
expect(isMinIO).toBe(true);
});
expect(result).toBe(true);
expect(host.mocks.rebuilder.$fetchLocal).toHaveBeenCalledWith(false, true);
}
);
it("should enable makeLocalChunkBeforeSync for non-MinIO remotes", () => {
it("prepares deduplicated local chunks for CouchDB", async () => {
const host = createHostMock();
const log = createLoggerMock();
host.mocks.setting.settings.remoteType = "CouchDB";
host.mocks.storageAccess.files.add(FlagFilesOriginal.FETCH_ALL);
host.mocks.ui.confirm.confirmWithMessage.mockResolvedValueOnce(SIMPLE_FETCH_STAGE1_DETAILED);
host.mocks.ui.dialogManager.openWithExplicitCancel.mockResolvedValueOnce({
vault: "identical",
backup: "backup_skipped",
extra: { preventFetchingConfig: true },
});
const settings = host.mocks.setting.currentSettings();
const isMinIO = settings.remoteType === REMOTE_MINIO;
const result = await createFetchAllFlagHandler(host as any, log).handle();
expect(isMinIO).toBe(false);
expect(result).toBe(true);
expect(host.mocks.rebuilder.$fetchLocal).toHaveBeenCalledWith(true, true);
});
});