Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2025-11-11T12:44:20.444Z\n"
"PO-Revision-Date: 2025-11-11T12:44:20.444Z\n"
"POT-Creation-Date: 2026-01-29T09:34:07.496Z\n"
"PO-Revision-Date: 2026-01-29T09:34:07.496Z\n"

msgid ""
"THIS NEW RELEASE INCLUDES SHARING SETTINGS PER INSTANCES. FOR THIS VERSION "
Expand Down Expand Up @@ -529,9 +529,6 @@ msgstr ""
msgid "Select with children subtree"
msgstr ""

msgid "Select"
msgstr ""

msgid "Set metadata custodian"
msgstr ""

Expand Down Expand Up @@ -1452,6 +1449,9 @@ msgstr ""
msgid "Due date"
msgstr ""

msgid "Select"
msgstr ""

msgid "An error ocurred while trying to access the required events"
msgstr ""

Expand Down
8 changes: 4 additions & 4 deletions i18n/es.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2025-11-11T12:44:20.444Z\n"
"POT-Creation-Date: 2026-01-29T07:30:29.800Z\n"
"PO-Revision-Date: 2020-07-10T06:53:30.625Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
Expand Down Expand Up @@ -530,9 +530,6 @@ msgstr ""
msgid "Select with children subtree"
msgstr ""

msgid "Select"
msgstr ""

msgid "Set metadata custodian"
msgstr ""

Expand Down Expand Up @@ -1456,6 +1453,9 @@ msgstr ""
msgid "Due date"
msgstr ""

msgid "Select"
msgstr ""

msgid "An error ocurred while trying to access the required events"
msgstr ""

Expand Down
8 changes: 4 additions & 4 deletions i18n/fr.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2025-11-11T12:44:20.444Z\n"
"POT-Creation-Date: 2026-01-29T07:30:29.800Z\n"
"PO-Revision-Date: 2020-07-10T06:53:30.625Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
Expand Down Expand Up @@ -530,9 +530,6 @@ msgstr ""
msgid "Select with children subtree"
msgstr ""

msgid "Select"
msgstr ""

msgid "Set metadata custodian"
msgstr ""

Expand Down Expand Up @@ -1456,6 +1453,9 @@ msgstr ""
msgid "Due date"
msgstr ""

msgid "Select"
msgstr ""

msgid "An error ocurred while trying to access the required events"
msgstr ""

Expand Down
8 changes: 4 additions & 4 deletions i18n/pt.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2025-11-11T12:44:20.444Z\n"
"POT-Creation-Date: 2026-01-29T07:30:29.800Z\n"
"PO-Revision-Date: 2020-07-10T06:53:30.625Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
Expand Down Expand Up @@ -530,9 +530,6 @@ msgstr ""
msgid "Select with children subtree"
msgstr ""

msgid "Select"
msgstr ""

msgid "Set metadata custodian"
msgstr ""

Expand Down Expand Up @@ -1456,6 +1453,9 @@ msgstr ""
msgid "Due date"
msgstr ""

msgid "Select"
msgstr ""

msgid "An error ocurred while trying to access the required events"
msgstr ""

Expand Down
11 changes: 6 additions & 5 deletions src/domain/data-store/DataStoreMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ export class DataStoreMetadata {
this.action = data.action;
}

static buildFromKeys(keysWithNamespaces: string[]): DataStoreMetadata[] {
const dataStoreIds = this.getDataStoreIds(keysWithNamespaces);
static buildFromKeys(dataStoreIds: string[], excludedDataStoreIds: string[]): DataStoreMetadata[] {
const excludedSet = new Set(excludedDataStoreIds);
const filteredDataStoreIds = dataStoreIds.filter(id => !excludedSet.has(id));

const namespaceAndKey = dataStoreIds.map(dataStoreId => {
const namespaceAndKey = filteredDataStoreIds.map(dataStoreId => {
const match = dataStoreId.split(DataStoreMetadata.NS_SEPARATOR);
if (!match) {
if (match.length !== 2) {
throw new Error(`dataStore value does not match expected format: ${dataStoreId}`);
}
const [namespace, key] = match;
Expand Down Expand Up @@ -109,7 +110,7 @@ export class DataStoreMetadata {
}

static getDataStoreIds(keys: string[]): string[] {
return keys.filter(id => id.includes(DataStoreMetadata.NS_SEPARATOR));
return keys.filter(id => this.isDataStoreId(id));
}

static isDataStoreId(id: string): boolean {
Expand Down
30 changes: 30 additions & 0 deletions src/domain/metadata/builders/MetadataPayloadBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,36 @@ export class MetadataPayloadBuilder {
return finalMetadataPackage;
}

public async buildDataStorePayload(
syncBuilder: SynchronizationBuilder,
remoteInstance: Instance
): Promise<DataStoreMetadata[]> {
const { metadataIds, excludedIds, syncParams, originInstance: originInstanceId } = syncBuilder;

const dataStoreIds = DataStoreMetadata.getDataStoreIds(metadataIds);
const excludedDataStoreIds = DataStoreMetadata.getDataStoreIds(excludedIds);
const dataStore = DataStoreMetadata.buildFromKeys(dataStoreIds, excludedDataStoreIds);

if (dataStore.length === 0) return [];

const originInstance = await this.getOriginInstance(originInstanceId);
const dataStoreRepository = this.repositoryFactory.dataStoreMetadataRepository(originInstance);

const dataStoreRemoteRepository = this.repositoryFactory.dataStoreMetadataRepository(remoteInstance);

const dataStoreLocal = await dataStoreRepository.get(dataStore);
const dataStoreRemote = await dataStoreRemoteRepository.get(dataStore);

const dataStorePayload = DataStoreMetadata.combine(metadataIds, dataStoreLocal, dataStoreRemote, {
action: syncParams?.mergeMode,
});

return syncParams?.includeSharingSettingsObjectsAndReferences ||
syncParams?.includeOnlySharingSettingsReferences
? dataStorePayload
: DataStoreMetadata.removeSharingSettings(dataStorePayload);
}

@cache()
public async getOriginInstance(originInstanceId: string): Promise<Instance> {
const instance = await this.getInstanceById(originInstanceId);
Expand Down
18 changes: 1 addition & 17 deletions src/domain/metadata/usecases/MetadataSyncUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,7 @@ export class MetadataSyncUseCase extends GenericSyncUseCase {
}

private async buildDataStorePayload(instance: Instance): Promise<DataStoreMetadata[]> {
const { metadataIds, syncParams } = this.builder;
const dataStore = DataStoreMetadata.buildFromKeys(metadataIds);
if (dataStore.length === 0) return [];

const dataStoreRepository = await this.getDataStoreMetadataRepository();
const dataStoreRemoteRepository = await this.getDataStoreMetadataRepository(instance);

const dataStoreLocal = await dataStoreRepository.get(dataStore);
const dataStoreRemote = await dataStoreRemoteRepository.get(dataStore);

const dataStorePayload = DataStoreMetadata.combine(metadataIds, dataStoreLocal, dataStoreRemote, {
action: syncParams?.mergeMode,
});
return syncParams?.includeSharingSettingsObjectsAndReferences ||
syncParams?.includeOnlySharingSettingsReferences
? dataStorePayload
: DataStoreMetadata.removeSharingSettings(dataStorePayload);
return this.metadataPayloadBuilder.buildDataStorePayload(this.builder, instance);
}

private async saveDataStorePayload(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import { SynchronizationResultType, SynchronizationType } from "../entities/Sync
import { PayloadMapper } from "../mapper/PayloadMapper";
import { GenericSyncUseCase } from "./GenericSyncUseCase";
import { MetadataPayloadBuilder } from "../../metadata/builders/MetadataPayloadBuilder";
import { DownloadRepository } from "../../storage/repositories/DownloadRepository";
import { DownloadItem, DownloadRepository } from "../../storage/repositories/DownloadRepository";
import { TransformationRepository } from "../../transformations/repositories/TransformationRepository";
import { EventsPayloadBuilder } from "../../events/builders/EventsPayloadBuilder";
import { AggregatedPayloadBuilder } from "../../aggregated/builders/AggregatedPayloadBuilder";
import { DataStoreMetadata } from "../../data-store/DataStoreMetadata";
import { isEmptyContent } from "../utils";

type DownloadErrors = string[];

Expand Down Expand Up @@ -84,11 +86,18 @@ export class DownloadPayloadFromSyncRuleUseCase implements UseCase {
return { name: item.name, content: payload };
})
);

if (files.length === 1) {
this.downloadRepository.downloadFile(files[0].name, files[0].content);
} else if (files.length > 1) {
await this.downloadRepository.downloadZippedFiles(`synchronization-${date}`, files);
const { metadataIds } = rule.builder;
const dataStoreIds = DataStoreMetadata.getDataStoreIds(metadataIds);
const dataStoreFiles: DownloadItem[] =
dataStoreIds.length > 0 ? await this.mapDatastoreToDownloadItems(rule) : [];

const cleanedFiles = files.filter(file => !isEmptyContent(file.content));
const allFiles = [...cleanedFiles, ...dataStoreFiles];

if (allFiles.length === 1) {
this.downloadRepository.downloadFile(allFiles[0].name, allFiles[0].content);
} else if (allFiles.length > 1) {
await this.downloadRepository.downloadZippedFiles(`synchronization-${date}`, allFiles);
}

if (errors.length === 0) {
Expand Down Expand Up @@ -200,6 +209,40 @@ export class DownloadPayloadFromSyncRuleUseCase implements UseCase {
return [...downloadItemsByEvents, ...downloadItemsByTEIS, ...downloadItemsByAggregated];
}

private async mapDatastoreToDownloadItems(rule: SynchronizationRule): Promise<DownloadItem[]> {
const date = moment().format("YYYYMMDDHHmm");

const { targetInstances: targetInstanceIds } = rule.builder;

const instanceRepository = this.repositoryFactory.instanceRepository(this.localInstance);

return promiseMap(targetInstanceIds, async remoteInstanceId => {
const remoteInstance = await instanceRepository.getById(remoteInstanceId);
if (!remoteInstance) throw new Error("Unable to read remote instance");

try {
const dataStorePayload = await this.metadataPayloadBuilder.buildDataStorePayload(
rule.builder,
remoteInstance
);

const downloadItem: DownloadItem = {
name: _(["synchronization", rule.name, "datastore", remoteInstance.name, date])
.compact()
.kebabCase(),
content: dataStorePayload.map(datastoreMetadata => ({
namespace: datastoreMetadata.namespace,
keys: datastoreMetadata.keys,
sharing: datastoreMetadata.sharing ?? undefined,
})),
};
return downloadItem;
} catch (error: unknown) {
throw new Error(`An error has ocurred while downloading datastore payload: ${error}`);
}
});
}

private async getSyncRule(params: DownloadPayloadParams): Promise<SynchronizationRule | undefined> {
switch (params.kind) {
case "syncRuleId": {
Expand Down
12 changes: 12 additions & 0 deletions src/domain/synchronization/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@ export function cleanOrgUnitPath(orgUnitPath?: string): string {
export function cleanOrgUnitPaths(orgUnitPaths: string[]): string[] {
return orgUnitPaths.map(cleanOrgUnitPath);
}

export function isEmptyContent(content: unknown): boolean {
if (!content || typeof content !== "object") return true;

return Object.values(content).every(
value =>
value === undefined ||
value === null ||
(Array.isArray(value) && value.length === 0) ||
(typeof value === "object" && Object.keys(value).length === 0)
);
}
Loading
Loading