@wishcbg/bobcode
Version:
基於店家同步功能的需求,讓店家能夠在不同的店家之間同步各項功能設定,並且能夠檢視各項功能的異動情況。 規劃出一套店家設定即程式碼的機制,讓店家能夠以 config file (.sac) 來定義各項功能設定,並且能夠還原店家各項功能設定。 暫且稱這個機制為「Settings as Code」「店家設定即程式碼」。
234 lines (233 loc) • 9.88 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyChange = void 0;
const jsonpath_plus_1 = require("jsonpath-plus");
const bobAPI_1 = __importDefault(require("./bobAPI"));
const index_1 = require("../resources/index");
const error_1 = require("./error");
// main flow
const applyChange = async (change, bobBaseUrl, bobShopId, bobToken, forceRemoveUnresolvedLogicIds) => {
try {
//#region 取得boblogic對應表
const bobApi = new bobAPI_1.default({
baseUrl: bobBaseUrl,
token: bobToken,
shopId: bobShopId,
});
const bobLogicIdSetMap = await (async () => {
const bobLogicIdSet = await bobApi.findLogicIdSet();
return new Map(bobLogicIdSet.map((item) => {
return [item.logicId, item.resourceId];
}));
})();
//#endregion
//#region 根據每個change 依序處理
const { resourceType, operation, logicId, description, diffs, payload, } = change;
const resourceSchema = index_1.allResourceSchemas[resourceType];
// 取得對應的 apiPath與 transformMethod
// 可考慮重構
const { transformMethod, baseApiPath, } = (() => {
let transformMethod = null;
let baseApiPath = null;
if (resourceSchema.relationType === 'single') {
transformMethod = resourceSchema.applySingleUpdateApiBodyTransform;
baseApiPath = resourceSchema.applySingleUpdateApiPath;
}
if (resourceSchema.relationType === 'multi') {
if (operation === 'create') {
transformMethod = resourceSchema.applyCreateApiBodyTransform;
baseApiPath = resourceSchema.applyCreateApiPath;
}
if (operation === 'update') {
transformMethod = resourceSchema.applyUpdateApiBodyTransform;
baseApiPath = resourceSchema.applyUpdateApiPath;
}
if (operation === 'delete') {
transformMethod = resourceSchema.applyDestroyApiBodyTransform;
baseApiPath = resourceSchema.applyDestroyApiPath;
}
}
return {
transformMethod,
baseApiPath,
};
})();
if (!baseApiPath) {
throw new error_1.BadRequestError(`找不到對應的apiPath: ${resourceType}`, 'APPLY_CHANGES_FAILED_WRONG_API_PATH');
}
// 1. 將logicId 轉換成對應的resourceId
const logicIdRelationFields = resourceSchema.relationResources
.map((resource) => {
return {
settingSchemaField: resource.settingSchemaField,
logicIdToResourceIdNewField: resource.logicIdToResourceIdNewField || resource.settingSchemaField,
useLogicIdToResourceId: resource.useLogicIdToResourceId ?? true,
acceptsNullValue: resource.acceptsNullValue ?? false,
};
});
// 這個要改名
// 處理acceptsNullValue要放在這裡
const transformPayloadLogicId = (() => {
if (!payload) {
return {};
}
const transformedBodyAfterReplacingLogicIds = JSON.parse(JSON.stringify(payload));
logicIdRelationFields.forEach(({ settingSchemaField: path, logicIdToResourceIdNewField: newPath, useLogicIdToResourceId, acceptsNullValue, }) => {
(0, jsonpath_plus_1.JSONPath)({
path,
json: transformedBodyAfterReplacingLogicIds,
callback: (value, payloadType, { parent, }) => {
const lastPath = path.split('.').pop();
const newLastPath = newPath.split('.').pop();
if (lastPath === undefined || newLastPath === undefined) {
throw new error_1.BadRequestError(`無法處理的path: ${path}`, 'APPLY_CHANGES_FAILED_WRONG_PATH');
}
if (value === null) {
if (acceptsNullValue) {
delete parent[lastPath];
parent[newLastPath] = null;
}
delete parent[lastPath];
return;
}
// 如果不是logicId,則不進行轉換
if (!useLogicIdToResourceId) {
delete parent[lastPath];
parent[newLastPath] = value;
return;
}
if (Array.isArray(value)) {
const resourceIds = [];
value.forEach((one) => {
const resourceId = bobLogicIdSetMap.get(one);
if (resourceId) {
resourceIds.push(resourceId);
}
else {
if (!forceRemoveUnresolvedLogicIds) {
throw new error_1.BadRequestError(`找不到對應的resourceId: ${lastPath}`, 'APPLY_CHANGES_FAILED_UNRESOLVED_LOGIC_ID');
}
}
});
delete parent[lastPath];
parent[newLastPath] = resourceIds;
}
else {
const resourceId = bobLogicIdSetMap.get(value);
if (resourceId) {
// 將logicId轉換成resourceId
delete parent[lastPath];
parent[newLastPath] = resourceId;
}
else {
if (!forceRemoveUnresolvedLogicIds) {
throw new error_1.BadRequestError(`找不到對應的resourceId: ${lastPath}`, 'APPLY_CHANGES_FAILED_UNRESOLVED_LOGIC_ID');
}
delete parent[lastPath];
}
}
}
});
});
return transformedBodyAfterReplacingLogicIds;
})();
// 2. 查看是否有對應的transform,進行轉換
const { apiPath, apiBody, } = await (async () => {
const resourceId = bobLogicIdSetMap.get(logicId);
if (transformMethod) {
return await transformMethod({
shopId: bobShopId,
resourceId: resourceId,
}, transformPayloadLogicId, bobApi);
}
else {
return {
apiPath: baseApiPath.replace(':shopId', bobShopId).replace(':resourceId', resourceId || ''),
apiBody: transformPayloadLogicId,
};
}
})();
// 3. 根據apiPath、method、body 進行請求
const httpMethod = (() => {
if (resourceSchema.relationType === 'single') {
return 'put';
}
if (operation === 'create') {
return 'post';
}
else if (operation === 'update') {
return 'put';
}
else if (operation === 'delete') {
return 'delete';
}
throw new error_1.BadRequestError(`不支援的操作: ${operation}`, 'APPLY_CHANGES_FAILED_WRONG_OPERATION');
})();
const response = await bobApi.applyChanges(apiPath, httpMethod, apiBody);
// 4. 根據response 進行對應的處理 ? 好像目前沒有要處理的
// 5. 將要修改logicId,存入boblogic對應表
if (operation === 'create') {
await bobApi.upsertLogicIdSet([
{
resourceType,
logicId,
resourceId: response.data.id,
}
]);
}
else if (operation === 'delete') {
await bobApi.upsertLogicIdSet([
{
resourceType,
logicId,
resourceId: null,
}
]);
}
// 6. 回傳結果
return {
status: 'success',
errorMessage: null,
errorCode: null,
resourceType,
operation,
logicId,
description,
diffs,
payload: apiBody
};
//#endregion
}
catch (error) {
console.error(`處理變更 ${change.logicId} 時發生錯誤:`, error);
const { message, code } = normalizeError(error);
const { resourceType, operation, logicId, description, diffs, payload } = change;
return {
status: 'failed',
errorMessage: message,
errorCode: code,
resourceType,
operation,
logicId,
description,
diffs,
payload,
};
}
};
exports.applyChange = applyChange;
function normalizeError(err) {
if (err instanceof Error) {
return {
message: err.message,
code: err.code ?? 'UNKNOWN_ERROR',
};
}
return {
message: String(err),
code: 'UNKNOWN_ERROR',
};
}