@azlib/cms 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1965 -114
- package/dist/index.d.cts +439 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +439 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1920 -115
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let exceljs = require("exceljs");
|
|
25
|
+
exceljs = __toESM(exceljs, 1);
|
|
2
26
|
//#region src/content/schema.ts
|
|
3
27
|
const fields = {
|
|
4
28
|
text(options) {
|
|
@@ -2857,7 +2881,7 @@ var EcommerceService = class {
|
|
|
2857
2881
|
};
|
|
2858
2882
|
//#endregion
|
|
2859
2883
|
//#region src/plugins/ecommerce/routes.ts
|
|
2860
|
-
function json$
|
|
2884
|
+
function json$2(data, status = 200) {
|
|
2861
2885
|
return new Response(JSON.stringify(data), {
|
|
2862
2886
|
status,
|
|
2863
2887
|
headers: {
|
|
@@ -2866,11 +2890,11 @@ function json$1(data, status = 200) {
|
|
|
2866
2890
|
}
|
|
2867
2891
|
});
|
|
2868
2892
|
}
|
|
2869
|
-
function badRequest$
|
|
2870
|
-
return json$
|
|
2893
|
+
function badRequest$2(message) {
|
|
2894
|
+
return json$2({ error: message }, 400);
|
|
2871
2895
|
}
|
|
2872
|
-
function notFound$
|
|
2873
|
-
return json$
|
|
2896
|
+
function notFound$2(message) {
|
|
2897
|
+
return json$2({ error: message }, 404);
|
|
2874
2898
|
}
|
|
2875
2899
|
function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
2876
2900
|
const prefix = (options.apiPrefix ?? "/api/ecommerce").replace(/\/+$/, "");
|
|
@@ -2893,7 +2917,7 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
|
2893
2917
|
const offsetStr = url.searchParams.get("offset");
|
|
2894
2918
|
const limit = limitStr ? parseInt(limitStr, 10) : 20;
|
|
2895
2919
|
const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
|
|
2896
|
-
return json$
|
|
2920
|
+
return json$2(await service.listProducts({
|
|
2897
2921
|
categorySlug,
|
|
2898
2922
|
categoryId,
|
|
2899
2923
|
tagSlug,
|
|
@@ -2909,7 +2933,7 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
|
2909
2933
|
offset
|
|
2910
2934
|
}));
|
|
2911
2935
|
} catch (err) {
|
|
2912
|
-
return badRequest$
|
|
2936
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
2913
2937
|
}
|
|
2914
2938
|
});
|
|
2915
2939
|
ctx.registerRoute("GET", `${prefix}/products/:id`, async (_req, { params, url }) => {
|
|
@@ -2921,32 +2945,32 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
|
2921
2945
|
product = await service.getProduct(id);
|
|
2922
2946
|
if (!product) product = await service.getProductBySlug(id);
|
|
2923
2947
|
}
|
|
2924
|
-
if (!product) return notFound$
|
|
2925
|
-
return json$
|
|
2948
|
+
if (!product) return notFound$2(`Product '${id}' not found`);
|
|
2949
|
+
return json$2(product);
|
|
2926
2950
|
});
|
|
2927
2951
|
ctx.registerRoute("POST", `${prefix}/products`, async (req) => {
|
|
2928
2952
|
try {
|
|
2929
2953
|
const body = await req.json();
|
|
2930
|
-
if (!body.title) return badRequest$
|
|
2931
|
-
if (body.price === void 0 || body.price < 0) return badRequest$
|
|
2932
|
-
return json$
|
|
2954
|
+
if (!body.title) return badRequest$2("Product 'title' is required.");
|
|
2955
|
+
if (body.price === void 0 || body.price < 0) return badRequest$2("Valid product 'price' is required.");
|
|
2956
|
+
return json$2(await service.createProduct(body), 201);
|
|
2933
2957
|
} catch (err) {
|
|
2934
|
-
return badRequest$
|
|
2958
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
2935
2959
|
}
|
|
2936
2960
|
});
|
|
2937
2961
|
ctx.registerRoute("PUT", `${prefix}/products/:id`, async (req, { params }) => {
|
|
2938
2962
|
try {
|
|
2939
2963
|
const body = await req.json();
|
|
2940
2964
|
const updated = await service.updateProduct(params.id, body);
|
|
2941
|
-
if (!updated) return notFound$
|
|
2942
|
-
return json$
|
|
2965
|
+
if (!updated) return notFound$2(`Product '${params.id}' not found.`);
|
|
2966
|
+
return json$2(updated);
|
|
2943
2967
|
} catch (err) {
|
|
2944
|
-
return badRequest$
|
|
2968
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
2945
2969
|
}
|
|
2946
2970
|
});
|
|
2947
2971
|
ctx.registerRoute("DELETE", `${prefix}/products/:id`, async (_req, { params }) => {
|
|
2948
|
-
if (!await service.deleteProduct(params.id)) return notFound$
|
|
2949
|
-
return json$
|
|
2972
|
+
if (!await service.deleteProduct(params.id)) return notFound$2(`Product '${params.id}' not found.`);
|
|
2973
|
+
return json$2({
|
|
2950
2974
|
success: true,
|
|
2951
2975
|
id: params.id
|
|
2952
2976
|
});
|
|
@@ -2954,8 +2978,8 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
|
2954
2978
|
ctx.registerRoute("POST", `${prefix}/products/:id/images`, async (req, { params }) => {
|
|
2955
2979
|
try {
|
|
2956
2980
|
const body = await req.json();
|
|
2957
|
-
if (!body.filename || !body.mimeType) return badRequest$
|
|
2958
|
-
return json$
|
|
2981
|
+
if (!body.filename || !body.mimeType) return badRequest$2("'filename' and 'mimeType' are required.");
|
|
2982
|
+
return json$2(await service.uploadProductImage(params.id, {
|
|
2959
2983
|
filename: body.filename,
|
|
2960
2984
|
mimeType: body.mimeType,
|
|
2961
2985
|
sizeBytes: body.sizeBytes ?? 0,
|
|
@@ -2967,68 +2991,68 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
|
2967
2991
|
isFeatured: body.isFeatured
|
|
2968
2992
|
}), 201);
|
|
2969
2993
|
} catch (err) {
|
|
2970
|
-
return badRequest$
|
|
2994
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
2971
2995
|
}
|
|
2972
2996
|
});
|
|
2973
2997
|
ctx.registerRoute("GET", `${prefix}/categories`, async (_req, { url }) => {
|
|
2974
2998
|
try {
|
|
2975
|
-
if (url.searchParams.get("tree") === "true") return json$
|
|
2999
|
+
if (url.searchParams.get("tree") === "true") return json$2(await service.getCategoryTree());
|
|
2976
3000
|
const parentId = url.searchParams.get("parentId");
|
|
2977
|
-
return json$
|
|
3001
|
+
return json$2(await service.getCategories({ parentId: parentId === "null" ? null : parentId ?? void 0 }));
|
|
2978
3002
|
} catch (err) {
|
|
2979
|
-
return badRequest$
|
|
3003
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
2980
3004
|
}
|
|
2981
3005
|
});
|
|
2982
3006
|
ctx.registerRoute("POST", `${prefix}/categories`, async (req) => {
|
|
2983
3007
|
try {
|
|
2984
3008
|
const body = await req.json();
|
|
2985
|
-
if (!body.name) return badRequest$
|
|
2986
|
-
return json$
|
|
3009
|
+
if (!body.name) return badRequest$2("Category 'name' is required.");
|
|
3010
|
+
return json$2(await service.createCategory(body), 201);
|
|
2987
3011
|
} catch (err) {
|
|
2988
|
-
return badRequest$
|
|
3012
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
2989
3013
|
}
|
|
2990
3014
|
});
|
|
2991
3015
|
if (options.enableDiscounts !== false) {
|
|
2992
3016
|
ctx.registerRoute("POST", `${prefix}/discounts`, async (req) => {
|
|
2993
3017
|
try {
|
|
2994
3018
|
const body = await req.json();
|
|
2995
|
-
if (!body.title || !body.code) return badRequest$
|
|
2996
|
-
if (body.value === void 0 || body.value < 0) return badRequest$
|
|
2997
|
-
return json$
|
|
3019
|
+
if (!body.title || !body.code) return badRequest$2("'title' and 'code' are required.");
|
|
3020
|
+
if (body.value === void 0 || body.value < 0) return badRequest$2("Valid discount 'value' is required.");
|
|
3021
|
+
return json$2(await service.createDiscount(body), 201);
|
|
2998
3022
|
} catch (err) {
|
|
2999
|
-
return badRequest$
|
|
3023
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
3000
3024
|
}
|
|
3001
3025
|
});
|
|
3002
3026
|
ctx.registerRoute("POST", `${prefix}/discounts/validate`, async (req) => {
|
|
3003
3027
|
try {
|
|
3004
3028
|
const body = await req.json();
|
|
3005
|
-
if (!body.code) return badRequest$
|
|
3029
|
+
if (!body.code) return badRequest$2("Discount 'code' is required.");
|
|
3006
3030
|
const subtotal = Number(body.subtotal ?? 0);
|
|
3007
3031
|
const productIds = Array.isArray(body.productIds) ? body.productIds : [];
|
|
3008
|
-
return json$
|
|
3032
|
+
return json$2(await service.validateDiscount(body.code, subtotal, productIds));
|
|
3009
3033
|
} catch (err) {
|
|
3010
|
-
return badRequest$
|
|
3034
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
3011
3035
|
}
|
|
3012
3036
|
});
|
|
3013
3037
|
}
|
|
3014
3038
|
ctx.registerRoute("POST", `${prefix}/cart/calculate`, async (req) => {
|
|
3015
3039
|
try {
|
|
3016
3040
|
const body = await req.json();
|
|
3017
|
-
if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest$
|
|
3018
|
-
return json$
|
|
3041
|
+
if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest$2("'items' array is required and must not be empty.");
|
|
3042
|
+
return json$2(await service.calculateCart(body));
|
|
3019
3043
|
} catch (err) {
|
|
3020
|
-
return badRequest$
|
|
3044
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
3021
3045
|
}
|
|
3022
3046
|
});
|
|
3023
3047
|
if (options.enableOrders !== false) {
|
|
3024
3048
|
ctx.registerRoute("POST", `${prefix}/orders`, async (req) => {
|
|
3025
3049
|
try {
|
|
3026
3050
|
const body = await req.json();
|
|
3027
|
-
if (!body.customerEmail) return badRequest$
|
|
3028
|
-
if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest$
|
|
3029
|
-
return json$
|
|
3051
|
+
if (!body.customerEmail) return badRequest$2("'customerEmail' is required.");
|
|
3052
|
+
if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest$2("'items' array is required.");
|
|
3053
|
+
return json$2(await service.createOrder(body), 201);
|
|
3030
3054
|
} catch (err) {
|
|
3031
|
-
return badRequest$
|
|
3055
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
3032
3056
|
}
|
|
3033
3057
|
});
|
|
3034
3058
|
ctx.registerRoute("GET", `${prefix}/orders/:id`, async (_req, { params, url }) => {
|
|
@@ -3040,18 +3064,18 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
|
|
|
3040
3064
|
order = await service.getOrder(id);
|
|
3041
3065
|
if (!order) order = await service.getOrderByNumber(id);
|
|
3042
3066
|
}
|
|
3043
|
-
if (!order) return notFound$
|
|
3044
|
-
return json$
|
|
3067
|
+
if (!order) return notFound$2(`Order '${id}' not found.`);
|
|
3068
|
+
return json$2(order);
|
|
3045
3069
|
});
|
|
3046
3070
|
ctx.registerRoute("PATCH", `${prefix}/orders/:id/status`, async (req, { params }) => {
|
|
3047
3071
|
try {
|
|
3048
3072
|
const body = await req.json();
|
|
3049
|
-
if (!body.status) return badRequest$
|
|
3073
|
+
if (!body.status) return badRequest$2("New 'status' is required.");
|
|
3050
3074
|
const updated = await service.updateOrderStatus(params.id, body.status, body.note);
|
|
3051
|
-
if (!updated) return notFound$
|
|
3052
|
-
return json$
|
|
3075
|
+
if (!updated) return notFound$2(`Order '${params.id}' not found.`);
|
|
3076
|
+
return json$2(updated);
|
|
3053
3077
|
} catch (err) {
|
|
3054
|
-
return badRequest$
|
|
3078
|
+
return badRequest$2(err instanceof Error ? err.message : String(err));
|
|
3055
3079
|
}
|
|
3056
3080
|
});
|
|
3057
3081
|
}
|
|
@@ -4285,7 +4309,7 @@ var HRMSService = class {
|
|
|
4285
4309
|
};
|
|
4286
4310
|
//#endregion
|
|
4287
4311
|
//#region src/plugins/hrms/routes.ts
|
|
4288
|
-
function json(data, status = 200) {
|
|
4312
|
+
function json$1(data, status = 200) {
|
|
4289
4313
|
return new Response(JSON.stringify(data), {
|
|
4290
4314
|
status,
|
|
4291
4315
|
headers: {
|
|
@@ -4294,11 +4318,11 @@ function json(data, status = 200) {
|
|
|
4294
4318
|
}
|
|
4295
4319
|
});
|
|
4296
4320
|
}
|
|
4297
|
-
function badRequest(message) {
|
|
4298
|
-
return json({ error: message }, 400);
|
|
4321
|
+
function badRequest$1(message) {
|
|
4322
|
+
return json$1({ error: message }, 400);
|
|
4299
4323
|
}
|
|
4300
|
-
function notFound(message) {
|
|
4301
|
-
return json({ error: message }, 404);
|
|
4324
|
+
function notFound$1(message) {
|
|
4325
|
+
return json$1({ error: message }, 404);
|
|
4302
4326
|
}
|
|
4303
4327
|
function registerHRMSRoutes(ctx, service, options = {}) {
|
|
4304
4328
|
const prefix = (options.apiPrefix ?? "/api/hrms").replace(/\/+$/, "");
|
|
@@ -4309,41 +4333,41 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4309
4333
|
const limitStr = url.searchParams.get("limit");
|
|
4310
4334
|
const page = pageStr ? parseInt(pageStr, 10) : void 0;
|
|
4311
4335
|
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
4312
|
-
return json(await service.listEmployers({
|
|
4336
|
+
return json$1(await service.listEmployers({
|
|
4313
4337
|
status,
|
|
4314
4338
|
page,
|
|
4315
4339
|
limit
|
|
4316
4340
|
}));
|
|
4317
4341
|
} catch (err) {
|
|
4318
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4342
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4319
4343
|
}
|
|
4320
4344
|
});
|
|
4321
4345
|
ctx.registerRoute("POST", `${prefix}/employers`, async (req) => {
|
|
4322
4346
|
try {
|
|
4323
4347
|
const body = await req.json();
|
|
4324
|
-
if (!body.companyName) return badRequest("companyName is required.");
|
|
4325
|
-
return json(await service.createEmployer(body), 201);
|
|
4348
|
+
if (!body.companyName) return badRequest$1("companyName is required.");
|
|
4349
|
+
return json$1(await service.createEmployer(body), 201);
|
|
4326
4350
|
} catch (err) {
|
|
4327
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4351
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4328
4352
|
}
|
|
4329
4353
|
});
|
|
4330
4354
|
ctx.registerRoute("GET", `${prefix}/employers/:id`, async (_req, { params }) => {
|
|
4331
4355
|
try {
|
|
4332
4356
|
const employer = await service.getEmployer(params.id);
|
|
4333
|
-
if (!employer) return notFound(`Employer '${params.id}' not found.`);
|
|
4334
|
-
return json(employer);
|
|
4357
|
+
if (!employer) return notFound$1(`Employer '${params.id}' not found.`);
|
|
4358
|
+
return json$1(employer);
|
|
4335
4359
|
} catch (err) {
|
|
4336
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4360
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4337
4361
|
}
|
|
4338
4362
|
});
|
|
4339
4363
|
ctx.registerRoute("PUT", `${prefix}/employers/:id`, async (req, { params }) => {
|
|
4340
4364
|
try {
|
|
4341
4365
|
const body = await req.json();
|
|
4342
4366
|
const updated = await service.updateEmployer(params.id, body);
|
|
4343
|
-
if (!updated) return notFound(`Employer '${params.id}' not found.`);
|
|
4344
|
-
return json(updated);
|
|
4367
|
+
if (!updated) return notFound$1(`Employer '${params.id}' not found.`);
|
|
4368
|
+
return json$1(updated);
|
|
4345
4369
|
} catch (err) {
|
|
4346
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4370
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4347
4371
|
}
|
|
4348
4372
|
});
|
|
4349
4373
|
ctx.registerRoute("GET", `${prefix}/employees`, async (_req, { url }) => {
|
|
@@ -4357,7 +4381,7 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4357
4381
|
const limitStr = url.searchParams.get("limit");
|
|
4358
4382
|
const page = pageStr ? parseInt(pageStr, 10) : void 0;
|
|
4359
4383
|
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
4360
|
-
return json(await service.listEmployees({
|
|
4384
|
+
return json$1(await service.listEmployees({
|
|
4361
4385
|
employerId,
|
|
4362
4386
|
department,
|
|
4363
4387
|
employmentType,
|
|
@@ -4367,84 +4391,84 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4367
4391
|
limit
|
|
4368
4392
|
}));
|
|
4369
4393
|
} catch (err) {
|
|
4370
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4394
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4371
4395
|
}
|
|
4372
4396
|
});
|
|
4373
4397
|
ctx.registerRoute("POST", `${prefix}/employees`, async (req) => {
|
|
4374
4398
|
try {
|
|
4375
4399
|
const body = await req.json();
|
|
4376
|
-
if (!body.employerId) return badRequest("employerId is required.");
|
|
4377
|
-
if (!body.employeeNumber) return badRequest("employeeNumber is required.");
|
|
4378
|
-
if (!body.firstName || !body.lastName) return badRequest("firstName and lastName are required.");
|
|
4379
|
-
if (!body.email) return badRequest("email is required.");
|
|
4380
|
-
return json(await service.createEmployee(body), 201);
|
|
4400
|
+
if (!body.employerId) return badRequest$1("employerId is required.");
|
|
4401
|
+
if (!body.employeeNumber) return badRequest$1("employeeNumber is required.");
|
|
4402
|
+
if (!body.firstName || !body.lastName) return badRequest$1("firstName and lastName are required.");
|
|
4403
|
+
if (!body.email) return badRequest$1("email is required.");
|
|
4404
|
+
return json$1(await service.createEmployee(body), 201);
|
|
4381
4405
|
} catch (err) {
|
|
4382
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4406
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4383
4407
|
}
|
|
4384
4408
|
});
|
|
4385
4409
|
ctx.registerRoute("GET", `${prefix}/employees/:id`, async (_req, { params }) => {
|
|
4386
4410
|
try {
|
|
4387
4411
|
const employee = await service.getEmployee(params.id);
|
|
4388
|
-
if (!employee) return notFound(`Employee '${params.id}' not found.`);
|
|
4389
|
-
return json(employee);
|
|
4412
|
+
if (!employee) return notFound$1(`Employee '${params.id}' not found.`);
|
|
4413
|
+
return json$1(employee);
|
|
4390
4414
|
} catch (err) {
|
|
4391
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4415
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4392
4416
|
}
|
|
4393
4417
|
});
|
|
4394
4418
|
ctx.registerRoute("PUT", `${prefix}/employees/:id`, async (req, { params }) => {
|
|
4395
4419
|
try {
|
|
4396
4420
|
const body = await req.json();
|
|
4397
4421
|
const updated = await service.updateEmployee(params.id, body);
|
|
4398
|
-
if (!updated) return notFound(`Employee '${params.id}' not found.`);
|
|
4399
|
-
return json(updated);
|
|
4422
|
+
if (!updated) return notFound$1(`Employee '${params.id}' not found.`);
|
|
4423
|
+
return json$1(updated);
|
|
4400
4424
|
} catch (err) {
|
|
4401
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4425
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4402
4426
|
}
|
|
4403
4427
|
});
|
|
4404
4428
|
ctx.registerRoute("DELETE", `${prefix}/employees/:id`, async (_req, { params }) => {
|
|
4405
4429
|
try {
|
|
4406
|
-
if (!await service.deleteEmployee(params.id)) return notFound(`Employee '${params.id}' not found.`);
|
|
4407
|
-
return json({ success: true });
|
|
4430
|
+
if (!await service.deleteEmployee(params.id)) return notFound$1(`Employee '${params.id}' not found.`);
|
|
4431
|
+
return json$1({ success: true });
|
|
4408
4432
|
} catch (err) {
|
|
4409
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4433
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4410
4434
|
}
|
|
4411
4435
|
});
|
|
4412
4436
|
ctx.registerRoute("GET", `${prefix}/employees/:id/leave-balance`, async (_req, { params, url }) => {
|
|
4413
4437
|
try {
|
|
4414
4438
|
const yearStr = url.searchParams.get("year");
|
|
4415
4439
|
const year = yearStr ? parseInt(yearStr, 10) : void 0;
|
|
4416
|
-
return json(await service.calculateLeaveBalance(params.id, year));
|
|
4440
|
+
return json$1(await service.calculateLeaveBalance(params.id, year));
|
|
4417
4441
|
} catch (err) {
|
|
4418
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4442
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4419
4443
|
}
|
|
4420
4444
|
});
|
|
4421
4445
|
ctx.registerRoute("GET", `${prefix}/employees/:id/direct-reports`, async (_req, { params }) => {
|
|
4422
4446
|
try {
|
|
4423
4447
|
const reports = await service.getDirectReports(params.id);
|
|
4424
|
-
return json({
|
|
4448
|
+
return json$1({
|
|
4425
4449
|
items: reports,
|
|
4426
4450
|
total: reports.length
|
|
4427
4451
|
});
|
|
4428
4452
|
} catch (err) {
|
|
4429
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4453
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4430
4454
|
}
|
|
4431
4455
|
});
|
|
4432
4456
|
ctx.registerRoute("POST", `${prefix}/attendance/check-in`, async (req) => {
|
|
4433
4457
|
try {
|
|
4434
4458
|
const body = await req.json();
|
|
4435
|
-
if (!body.employeeId) return badRequest("employeeId is required.");
|
|
4436
|
-
return json(await service.checkIn(body), 201);
|
|
4459
|
+
if (!body.employeeId) return badRequest$1("employeeId is required.");
|
|
4460
|
+
return json$1(await service.checkIn(body), 201);
|
|
4437
4461
|
} catch (err) {
|
|
4438
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4462
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4439
4463
|
}
|
|
4440
4464
|
});
|
|
4441
4465
|
ctx.registerRoute("POST", `${prefix}/attendance/check-out`, async (req) => {
|
|
4442
4466
|
try {
|
|
4443
4467
|
const body = await req.json();
|
|
4444
|
-
if (!body.employeeId) return badRequest("employeeId is required.");
|
|
4445
|
-
return json(await service.checkOut(body));
|
|
4468
|
+
if (!body.employeeId) return badRequest$1("employeeId is required.");
|
|
4469
|
+
return json$1(await service.checkOut(body));
|
|
4446
4470
|
} catch (err) {
|
|
4447
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4471
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4448
4472
|
}
|
|
4449
4473
|
});
|
|
4450
4474
|
ctx.registerRoute("GET", `${prefix}/attendance`, async (_req, { url }) => {
|
|
@@ -4459,7 +4483,7 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4459
4483
|
const limitStr = url.searchParams.get("limit");
|
|
4460
4484
|
const page = pageStr ? parseInt(pageStr, 10) : void 0;
|
|
4461
4485
|
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
4462
|
-
return json(await service.listAttendance({
|
|
4486
|
+
return json$1(await service.listAttendance({
|
|
4463
4487
|
employerId,
|
|
4464
4488
|
employeeId,
|
|
4465
4489
|
date,
|
|
@@ -4470,46 +4494,46 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4470
4494
|
limit
|
|
4471
4495
|
}));
|
|
4472
4496
|
} catch (err) {
|
|
4473
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4497
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4474
4498
|
}
|
|
4475
4499
|
});
|
|
4476
4500
|
ctx.registerRoute("POST", `${prefix}/attendance/manual`, async (req) => {
|
|
4477
4501
|
try {
|
|
4478
4502
|
const body = await req.json();
|
|
4479
|
-
if (!body.employerId || !body.employeeId || !body.date || !body.checkInAt) return badRequest("employerId, employeeId, date, and checkInAt are required.");
|
|
4480
|
-
return json(await service.recordAttendanceManual(body), 201);
|
|
4503
|
+
if (!body.employerId || !body.employeeId || !body.date || !body.checkInAt) return badRequest$1("employerId, employeeId, date, and checkInAt are required.");
|
|
4504
|
+
return json$1(await service.recordAttendanceManual(body), 201);
|
|
4481
4505
|
} catch (err) {
|
|
4482
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4506
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4483
4507
|
}
|
|
4484
4508
|
});
|
|
4485
4509
|
ctx.registerRoute("GET", `${prefix}/leave-types`, async (_req, { url }) => {
|
|
4486
4510
|
try {
|
|
4487
4511
|
const employerId = url.searchParams.get("employerId") ?? void 0;
|
|
4488
4512
|
const types = await service.listLeaveTypes(employerId);
|
|
4489
|
-
return json({
|
|
4513
|
+
return json$1({
|
|
4490
4514
|
items: types,
|
|
4491
4515
|
total: types.length
|
|
4492
4516
|
});
|
|
4493
4517
|
} catch (err) {
|
|
4494
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4518
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4495
4519
|
}
|
|
4496
4520
|
});
|
|
4497
4521
|
ctx.registerRoute("POST", `${prefix}/leave-types`, async (req) => {
|
|
4498
4522
|
try {
|
|
4499
4523
|
const body = await req.json();
|
|
4500
|
-
if (!body.name || !body.code || body.daysAllowedPerYear === void 0) return badRequest("name, code, and daysAllowedPerYear are required.");
|
|
4501
|
-
return json(await service.createLeaveType(body), 201);
|
|
4524
|
+
if (!body.name || !body.code || body.daysAllowedPerYear === void 0) return badRequest$1("name, code, and daysAllowedPerYear are required.");
|
|
4525
|
+
return json$1(await service.createLeaveType(body), 201);
|
|
4502
4526
|
} catch (err) {
|
|
4503
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4527
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4504
4528
|
}
|
|
4505
4529
|
});
|
|
4506
4530
|
ctx.registerRoute("GET", `${prefix}/leave-types/:id`, async (_req, { params }) => {
|
|
4507
4531
|
try {
|
|
4508
4532
|
const leaveType = await service.getLeaveType(params.id);
|
|
4509
|
-
if (!leaveType) return notFound(`Leave type '${params.id}' not found.`);
|
|
4510
|
-
return json(leaveType);
|
|
4533
|
+
if (!leaveType) return notFound$1(`Leave type '${params.id}' not found.`);
|
|
4534
|
+
return json$1(leaveType);
|
|
4511
4535
|
} catch (err) {
|
|
4512
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4536
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4513
4537
|
}
|
|
4514
4538
|
});
|
|
4515
4539
|
ctx.registerRoute("GET", `${prefix}/leave-requests`, async (_req, { url }) => {
|
|
@@ -4524,7 +4548,7 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4524
4548
|
const limitStr = url.searchParams.get("limit");
|
|
4525
4549
|
const page = pageStr ? parseInt(pageStr, 10) : void 0;
|
|
4526
4550
|
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
4527
|
-
return json(await service.listLeaveRequests({
|
|
4551
|
+
return json$1(await service.listLeaveRequests({
|
|
4528
4552
|
employerId,
|
|
4529
4553
|
employeeId,
|
|
4530
4554
|
leaveTypeId,
|
|
@@ -4534,47 +4558,47 @@ function registerHRMSRoutes(ctx, service, options = {}) {
|
|
|
4534
4558
|
limit
|
|
4535
4559
|
}));
|
|
4536
4560
|
} catch (err) {
|
|
4537
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4561
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4538
4562
|
}
|
|
4539
4563
|
});
|
|
4540
4564
|
ctx.registerRoute("POST", `${prefix}/leave-requests`, async (req) => {
|
|
4541
4565
|
try {
|
|
4542
4566
|
const body = await req.json();
|
|
4543
|
-
if (!body.employeeId || !body.leaveTypeId || !body.startDate || !body.endDate) return badRequest("employeeId, leaveTypeId, startDate, and endDate are required.");
|
|
4544
|
-
return json(await service.requestLeave(body), 201);
|
|
4567
|
+
if (!body.employeeId || !body.leaveTypeId || !body.startDate || !body.endDate) return badRequest$1("employeeId, leaveTypeId, startDate, and endDate are required.");
|
|
4568
|
+
return json$1(await service.requestLeave(body), 201);
|
|
4545
4569
|
} catch (err) {
|
|
4546
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4570
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4547
4571
|
}
|
|
4548
4572
|
});
|
|
4549
4573
|
ctx.registerRoute("POST", `${prefix}/leave-requests/:id/approve`, async (req, { params }) => {
|
|
4550
4574
|
try {
|
|
4551
4575
|
const approverId = (await req.json().catch(() => ({}))).approverId ?? "admin";
|
|
4552
|
-
return json(await service.approveLeave({
|
|
4576
|
+
return json$1(await service.approveLeave({
|
|
4553
4577
|
requestId: params.id,
|
|
4554
4578
|
approverId
|
|
4555
4579
|
}));
|
|
4556
4580
|
} catch (err) {
|
|
4557
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4581
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4558
4582
|
}
|
|
4559
4583
|
});
|
|
4560
4584
|
ctx.registerRoute("POST", `${prefix}/leave-requests/:id/reject`, async (req, { params }) => {
|
|
4561
4585
|
try {
|
|
4562
4586
|
const body = await req.json().catch(() => ({}));
|
|
4563
4587
|
const approverId = body.approverId ?? "admin";
|
|
4564
|
-
return json(await service.rejectLeave({
|
|
4588
|
+
return json$1(await service.rejectLeave({
|
|
4565
4589
|
requestId: params.id,
|
|
4566
4590
|
approverId,
|
|
4567
4591
|
reason: body.reason
|
|
4568
4592
|
}));
|
|
4569
4593
|
} catch (err) {
|
|
4570
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4594
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4571
4595
|
}
|
|
4572
4596
|
});
|
|
4573
4597
|
ctx.registerRoute("POST", `${prefix}/leave-requests/:id/cancel`, async (_req, { params }) => {
|
|
4574
4598
|
try {
|
|
4575
|
-
return json(await service.cancelLeave(params.id));
|
|
4599
|
+
return json$1(await service.cancelLeave(params.id));
|
|
4576
4600
|
} catch (err) {
|
|
4577
|
-
return badRequest(err instanceof Error ? err.message : String(err));
|
|
4601
|
+
return badRequest$1(err instanceof Error ? err.message : String(err));
|
|
4578
4602
|
}
|
|
4579
4603
|
});
|
|
4580
4604
|
}
|
|
@@ -4826,6 +4850,1810 @@ function getHRMSService(engine, options) {
|
|
|
4826
4850
|
return service;
|
|
4827
4851
|
}
|
|
4828
4852
|
//#endregion
|
|
4853
|
+
//#region src/plugins/transfer/parsers/json.ts
|
|
4854
|
+
/**
|
|
4855
|
+
* @azlib/cms - Universal JSON Data Parser
|
|
4856
|
+
*/
|
|
4857
|
+
function parseJsonSource(input) {
|
|
4858
|
+
if (Array.isArray(input)) return input.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item)));
|
|
4859
|
+
let text;
|
|
4860
|
+
if (typeof input === "string") text = input.trim();
|
|
4861
|
+
else if (input instanceof Uint8Array || Buffer.isBuffer(input)) text = new TextDecoder("utf-8").decode(input).trim();
|
|
4862
|
+
else if (input && typeof input === "object") {
|
|
4863
|
+
const obj = input;
|
|
4864
|
+
if (Array.isArray(obj.data)) return parseJsonSource(obj.data);
|
|
4865
|
+
if (Array.isArray(obj.items)) return parseJsonSource(obj.items);
|
|
4866
|
+
return [obj];
|
|
4867
|
+
} else throw new Error("[TransferParser] Invalid JSON input: expected string, buffer, or array.");
|
|
4868
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
4869
|
+
if (!text) return [];
|
|
4870
|
+
try {
|
|
4871
|
+
const parsed = JSON.parse(text);
|
|
4872
|
+
if (Array.isArray(parsed)) return parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item)));
|
|
4873
|
+
if (parsed && typeof parsed === "object") {
|
|
4874
|
+
if (Array.isArray(parsed.data)) return parseJsonSource(parsed.data);
|
|
4875
|
+
if (Array.isArray(parsed.items)) return parseJsonSource(parsed.items);
|
|
4876
|
+
return [parsed];
|
|
4877
|
+
}
|
|
4878
|
+
return [];
|
|
4879
|
+
} catch {
|
|
4880
|
+
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
4881
|
+
const records = [];
|
|
4882
|
+
for (const line of lines) try {
|
|
4883
|
+
const item = JSON.parse(line);
|
|
4884
|
+
if (item && typeof item === "object" && !Array.isArray(item)) records.push(item);
|
|
4885
|
+
} catch (err) {
|
|
4886
|
+
throw new Error(`[TransferParser] Failed to parse JSON source: ${err instanceof Error ? err.message : String(err)}`);
|
|
4887
|
+
}
|
|
4888
|
+
return records;
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
//#endregion
|
|
4892
|
+
//#region src/plugins/transfer/parsers/csv.ts
|
|
4893
|
+
/**
|
|
4894
|
+
* @azlib/cms - Universal RFC 4180 CSV Data Parser
|
|
4895
|
+
*/
|
|
4896
|
+
function parseCsvSource(input) {
|
|
4897
|
+
let text;
|
|
4898
|
+
if (typeof input === "string") text = input;
|
|
4899
|
+
else if (input instanceof Uint8Array || Buffer.isBuffer(input)) text = new TextDecoder("utf-8").decode(input);
|
|
4900
|
+
else throw new Error("[TransferParser] Invalid CSV input: expected string or buffer.");
|
|
4901
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
4902
|
+
const rows = parseCsvRows(text);
|
|
4903
|
+
if (rows.length === 0) return [];
|
|
4904
|
+
const headers = rows[0].map((h, i) => {
|
|
4905
|
+
return h.trim() || `column_${i + 1}`;
|
|
4906
|
+
});
|
|
4907
|
+
const records = [];
|
|
4908
|
+
for (let r = 1; r < rows.length; r++) {
|
|
4909
|
+
const row = rows[r];
|
|
4910
|
+
if (row.length === 0 || row.length === 1 && row[0].trim() === "") continue;
|
|
4911
|
+
const record = {};
|
|
4912
|
+
for (let c = 0; c < headers.length; c++) {
|
|
4913
|
+
const header = headers[c];
|
|
4914
|
+
record[header] = coerceCsvValue(c < row.length ? row[c].trim() : "");
|
|
4915
|
+
}
|
|
4916
|
+
records.push(record);
|
|
4917
|
+
}
|
|
4918
|
+
return records;
|
|
4919
|
+
}
|
|
4920
|
+
/**
|
|
4921
|
+
* Tokenize CSV into 2D array of string cells following RFC 4180.
|
|
4922
|
+
*/
|
|
4923
|
+
function parseCsvRows(text) {
|
|
4924
|
+
const rows = [];
|
|
4925
|
+
let currentRow = [];
|
|
4926
|
+
let currentCell = "";
|
|
4927
|
+
let insideQuotes = false;
|
|
4928
|
+
let i = 0;
|
|
4929
|
+
const len = text.length;
|
|
4930
|
+
while (i < len) {
|
|
4931
|
+
const char = text[i];
|
|
4932
|
+
if (insideQuotes) if (char === "\"") if (i + 1 < len && text[i + 1] === "\"") {
|
|
4933
|
+
currentCell += "\"";
|
|
4934
|
+
i += 2;
|
|
4935
|
+
continue;
|
|
4936
|
+
} else {
|
|
4937
|
+
insideQuotes = false;
|
|
4938
|
+
i++;
|
|
4939
|
+
continue;
|
|
4940
|
+
}
|
|
4941
|
+
else {
|
|
4942
|
+
currentCell += char;
|
|
4943
|
+
i++;
|
|
4944
|
+
continue;
|
|
4945
|
+
}
|
|
4946
|
+
if (char === "\"") {
|
|
4947
|
+
insideQuotes = true;
|
|
4948
|
+
i++;
|
|
4949
|
+
continue;
|
|
4950
|
+
}
|
|
4951
|
+
if (char === ",") {
|
|
4952
|
+
currentRow.push(currentCell);
|
|
4953
|
+
currentCell = "";
|
|
4954
|
+
i++;
|
|
4955
|
+
continue;
|
|
4956
|
+
}
|
|
4957
|
+
if (char === "\r") {
|
|
4958
|
+
if (i + 1 < len && text[i + 1] === "\n") i++;
|
|
4959
|
+
currentRow.push(currentCell);
|
|
4960
|
+
rows.push(currentRow);
|
|
4961
|
+
currentRow = [];
|
|
4962
|
+
currentCell = "";
|
|
4963
|
+
i++;
|
|
4964
|
+
continue;
|
|
4965
|
+
}
|
|
4966
|
+
if (char === "\n") {
|
|
4967
|
+
currentRow.push(currentCell);
|
|
4968
|
+
rows.push(currentRow);
|
|
4969
|
+
currentRow = [];
|
|
4970
|
+
currentCell = "";
|
|
4971
|
+
i++;
|
|
4972
|
+
continue;
|
|
4973
|
+
}
|
|
4974
|
+
currentCell += char;
|
|
4975
|
+
i++;
|
|
4976
|
+
}
|
|
4977
|
+
if (currentCell.length > 0 || currentRow.length > 0) {
|
|
4978
|
+
currentRow.push(currentCell);
|
|
4979
|
+
rows.push(currentRow);
|
|
4980
|
+
}
|
|
4981
|
+
return rows;
|
|
4982
|
+
}
|
|
4983
|
+
/**
|
|
4984
|
+
* Coerce obvious numeric / boolean CSV strings to primitives when lossless.
|
|
4985
|
+
*/
|
|
4986
|
+
function coerceCsvValue(val) {
|
|
4987
|
+
if (val === "") return "";
|
|
4988
|
+
if (val.toLowerCase() === "true") return true;
|
|
4989
|
+
if (val.toLowerCase() === "false") return false;
|
|
4990
|
+
if (val.toLowerCase() === "null") return null;
|
|
4991
|
+
if (/^-?\d+(\.\d+)?$/.test(val)) {
|
|
4992
|
+
if (val.length > 1 && val.startsWith("0") && !val.startsWith("0.")) return val;
|
|
4993
|
+
const num = Number(val);
|
|
4994
|
+
if (!Number.isNaN(num)) return num;
|
|
4995
|
+
}
|
|
4996
|
+
return val;
|
|
4997
|
+
}
|
|
4998
|
+
//#endregion
|
|
4999
|
+
//#region src/plugins/transfer/parsers/excel.ts
|
|
5000
|
+
/**
|
|
5001
|
+
* @azlib/cms - Universal Excel (.xlsx) Data Parser using ExcelJS
|
|
5002
|
+
*/
|
|
5003
|
+
async function parseExcelSource(input, options = {}) {
|
|
5004
|
+
const workbook = new exceljs.default.Workbook();
|
|
5005
|
+
let buffer;
|
|
5006
|
+
if (Buffer.isBuffer(input)) buffer = input;
|
|
5007
|
+
else if (input instanceof Uint8Array) buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
5008
|
+
else if (input instanceof ArrayBuffer) buffer = Buffer.from(input);
|
|
5009
|
+
else throw new Error("[TransferParser] Invalid Excel input: expected Buffer, Uint8Array, or ArrayBuffer.");
|
|
5010
|
+
await workbook.xlsx.load(buffer);
|
|
5011
|
+
const sheetNames = workbook.worksheets.map((s) => s.name);
|
|
5012
|
+
if (sheetNames.length === 0) return {
|
|
5013
|
+
sheets: [],
|
|
5014
|
+
selectedSheet: "",
|
|
5015
|
+
records: []
|
|
5016
|
+
};
|
|
5017
|
+
let worksheet = options.sheetName ? workbook.getWorksheet(options.sheetName) : void 0;
|
|
5018
|
+
if (!worksheet) worksheet = workbook.worksheets[0];
|
|
5019
|
+
const selectedSheet = worksheet.name;
|
|
5020
|
+
const records = [];
|
|
5021
|
+
const headerRow = worksheet.getRow(1);
|
|
5022
|
+
const headers = [];
|
|
5023
|
+
headerRow.eachCell({ includeEmpty: false }, (cell, colNumber) => {
|
|
5024
|
+
let headerText = String(cell.value ?? "").trim();
|
|
5025
|
+
if (!headerText) headerText = `column_${colNumber}`;
|
|
5026
|
+
headers[colNumber] = headerText;
|
|
5027
|
+
});
|
|
5028
|
+
if (headers.length === 0) return {
|
|
5029
|
+
sheets: sheetNames,
|
|
5030
|
+
selectedSheet,
|
|
5031
|
+
records: []
|
|
5032
|
+
};
|
|
5033
|
+
const rowCount = worksheet.rowCount;
|
|
5034
|
+
for (let r = 2; r <= rowCount; r++) {
|
|
5035
|
+
const row = worksheet.getRow(r);
|
|
5036
|
+
let hasValues = false;
|
|
5037
|
+
const record = {};
|
|
5038
|
+
for (let c = 1; c < headers.length; c++) {
|
|
5039
|
+
const header = headers[c];
|
|
5040
|
+
if (!header) continue;
|
|
5041
|
+
const val = extractCellValue(row.getCell(c).value);
|
|
5042
|
+
if (val !== void 0 && val !== null && val !== "") hasValues = true;
|
|
5043
|
+
record[header] = val;
|
|
5044
|
+
}
|
|
5045
|
+
if (hasValues) records.push(record);
|
|
5046
|
+
}
|
|
5047
|
+
return {
|
|
5048
|
+
sheets: sheetNames,
|
|
5049
|
+
selectedSheet,
|
|
5050
|
+
records
|
|
5051
|
+
};
|
|
5052
|
+
}
|
|
5053
|
+
/**
|
|
5054
|
+
* Normalizes ExcelJS cell values (handles formulas, dates, rich text, hyperlinks).
|
|
5055
|
+
*/
|
|
5056
|
+
function extractCellValue(val) {
|
|
5057
|
+
if (val === void 0 || val === null) return null;
|
|
5058
|
+
if (val instanceof Date) return val.toISOString();
|
|
5059
|
+
if (typeof val === "object") {
|
|
5060
|
+
if ("formula" in val) return extractCellValue(val.result);
|
|
5061
|
+
if ("text" in val && "hyperlink" in val) return val.text;
|
|
5062
|
+
if ("richText" in val && Array.isArray(val.richText)) return val.richText.map((t) => t.text).join("");
|
|
5063
|
+
if ("error" in val) return null;
|
|
5064
|
+
}
|
|
5065
|
+
return val;
|
|
5066
|
+
}
|
|
5067
|
+
//#endregion
|
|
5068
|
+
//#region src/plugins/transfer/parsers/index.ts
|
|
5069
|
+
/**
|
|
5070
|
+
* Detect the format of an input payload if not explicitly provided.
|
|
5071
|
+
*/
|
|
5072
|
+
function detectFormat(input, fileName) {
|
|
5073
|
+
if (Array.isArray(input)) return "json";
|
|
5074
|
+
if (fileName) {
|
|
5075
|
+
const lower = fileName.toLowerCase();
|
|
5076
|
+
if (lower.endsWith(".xlsx") || lower.endsWith(".xls")) return "excel";
|
|
5077
|
+
if (lower.endsWith(".csv")) return "csv";
|
|
5078
|
+
if (lower.endsWith(".json") || lower.endsWith(".ndjson")) return "json";
|
|
5079
|
+
}
|
|
5080
|
+
if (input instanceof Uint8Array || Buffer.isBuffer(input)) {
|
|
5081
|
+
if (input.length >= 4 && input[0] === 80 && input[1] === 75 && input[2] === 3 && input[3] === 4) return "excel";
|
|
5082
|
+
try {
|
|
5083
|
+
const head = new TextDecoder("utf-8").decode(input.slice(0, 100)).trim();
|
|
5084
|
+
if (head.startsWith("{") || head.startsWith("[")) return "json";
|
|
5085
|
+
if (head.includes(",") && head.includes("\n")) return "csv";
|
|
5086
|
+
} catch {}
|
|
5087
|
+
}
|
|
5088
|
+
if (typeof input === "string") {
|
|
5089
|
+
const trimmed = input.trim();
|
|
5090
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
5091
|
+
if (trimmed.includes(",") || trimmed.includes("\n")) return "csv";
|
|
5092
|
+
}
|
|
5093
|
+
return "json";
|
|
5094
|
+
}
|
|
5095
|
+
/**
|
|
5096
|
+
* Universal source parser that handles JSON, CSV, and Excel (.xlsx).
|
|
5097
|
+
*/
|
|
5098
|
+
async function parseSource(input, options = {}) {
|
|
5099
|
+
switch (options.format || detectFormat(input, options.fileName)) {
|
|
5100
|
+
case "excel": {
|
|
5101
|
+
const excelResult = await parseExcelSource(input, { sheetName: options.sheetName });
|
|
5102
|
+
return {
|
|
5103
|
+
format: "excel",
|
|
5104
|
+
sheets: excelResult.sheets,
|
|
5105
|
+
selectedSheet: excelResult.selectedSheet,
|
|
5106
|
+
records: excelResult.records
|
|
5107
|
+
};
|
|
5108
|
+
}
|
|
5109
|
+
case "csv": return {
|
|
5110
|
+
format: "csv",
|
|
5111
|
+
records: parseCsvSource(input)
|
|
5112
|
+
};
|
|
5113
|
+
default: return {
|
|
5114
|
+
format: "json",
|
|
5115
|
+
records: parseJsonSource(input)
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
//#endregion
|
|
5120
|
+
//#region src/plugins/transfer/serializers/json.ts
|
|
5121
|
+
function serializeJson(records, options = {}) {
|
|
5122
|
+
const indent = options.pretty !== false ? 2 : void 0;
|
|
5123
|
+
return JSON.stringify(records, null, indent);
|
|
5124
|
+
}
|
|
5125
|
+
//#endregion
|
|
5126
|
+
//#region src/plugins/transfer/serializers/csv.ts
|
|
5127
|
+
function serializeCsv(records, options = {}) {
|
|
5128
|
+
if (records.length === 0) return "";
|
|
5129
|
+
let columns = options.columns;
|
|
5130
|
+
if (!columns || columns.length === 0) {
|
|
5131
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5132
|
+
for (const record of records) for (const key of Object.keys(record)) keys.add(key);
|
|
5133
|
+
columns = Array.from(keys).map((k) => ({
|
|
5134
|
+
key: k,
|
|
5135
|
+
header: k
|
|
5136
|
+
}));
|
|
5137
|
+
}
|
|
5138
|
+
const lines = [];
|
|
5139
|
+
lines.push(columns.map((c) => escapeCsvCell(c.header)).join(","));
|
|
5140
|
+
for (const record of records) {
|
|
5141
|
+
const rowCells = columns.map((col) => {
|
|
5142
|
+
const val = record[col.key];
|
|
5143
|
+
return escapeCsvCell(formatCsvValue(val));
|
|
5144
|
+
});
|
|
5145
|
+
lines.push(rowCells.join(","));
|
|
5146
|
+
}
|
|
5147
|
+
return lines.join("\r\n");
|
|
5148
|
+
}
|
|
5149
|
+
function formatCsvValue(val) {
|
|
5150
|
+
if (val === void 0 || val === null) return "";
|
|
5151
|
+
if (typeof val === "object") {
|
|
5152
|
+
if (val instanceof Date) return val.toISOString();
|
|
5153
|
+
return JSON.stringify(val);
|
|
5154
|
+
}
|
|
5155
|
+
return String(val);
|
|
5156
|
+
}
|
|
5157
|
+
function escapeCsvCell(cell) {
|
|
5158
|
+
if (cell.includes(",") || cell.includes("\"") || cell.includes("\n") || cell.includes("\r")) return `"${cell.replace(/"/g, "\"\"")}"`;
|
|
5159
|
+
return cell;
|
|
5160
|
+
}
|
|
5161
|
+
//#endregion
|
|
5162
|
+
//#region src/plugins/transfer/serializers/excel.ts
|
|
5163
|
+
/**
|
|
5164
|
+
* @azlib/cms - Universal Excel (.xlsx) Serializer using ExcelJS
|
|
5165
|
+
*/
|
|
5166
|
+
async function serializeExcel(records, options = {}) {
|
|
5167
|
+
const workbook = new exceljs.default.Workbook();
|
|
5168
|
+
const sheetName = (options.sheetName || "Export").replace(/[:\\/?*\[\]]/g, "_");
|
|
5169
|
+
const worksheet = workbook.addWorksheet(sheetName.slice(0, 31));
|
|
5170
|
+
let columns = options.columns;
|
|
5171
|
+
if (!columns || columns.length === 0) {
|
|
5172
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5173
|
+
for (const record of records) for (const key of Object.keys(record)) keys.add(key);
|
|
5174
|
+
columns = Array.from(keys).map((k) => ({
|
|
5175
|
+
key: k,
|
|
5176
|
+
header: formatHeaderLabel(k)
|
|
5177
|
+
}));
|
|
5178
|
+
}
|
|
5179
|
+
worksheet.columns = columns.map((col) => {
|
|
5180
|
+
let maxLen = col.header.length;
|
|
5181
|
+
for (let i = 0; i < Math.min(records.length, 100); i++) {
|
|
5182
|
+
const val = records[i][col.key];
|
|
5183
|
+
if (val !== void 0 && val !== null) {
|
|
5184
|
+
const str = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
5185
|
+
if (str.length > maxLen) maxLen = Math.min(str.length, 50);
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
return {
|
|
5189
|
+
header: col.header,
|
|
5190
|
+
key: col.key,
|
|
5191
|
+
width: Math.max(maxLen + 4, col.width ?? 12)
|
|
5192
|
+
};
|
|
5193
|
+
});
|
|
5194
|
+
const headerRow = worksheet.getRow(1);
|
|
5195
|
+
headerRow.height = 28;
|
|
5196
|
+
headerRow.eachCell((cell) => {
|
|
5197
|
+
cell.font = {
|
|
5198
|
+
bold: true,
|
|
5199
|
+
color: { argb: "FF1E293B" },
|
|
5200
|
+
size: 11
|
|
5201
|
+
};
|
|
5202
|
+
cell.fill = {
|
|
5203
|
+
type: "pattern",
|
|
5204
|
+
pattern: "solid",
|
|
5205
|
+
fgColor: { argb: "FFF1F5F9" }
|
|
5206
|
+
};
|
|
5207
|
+
cell.alignment = {
|
|
5208
|
+
vertical: "middle",
|
|
5209
|
+
horizontal: "center",
|
|
5210
|
+
wrapText: true
|
|
5211
|
+
};
|
|
5212
|
+
cell.border = {
|
|
5213
|
+
top: {
|
|
5214
|
+
style: "thin",
|
|
5215
|
+
color: { argb: "FFE2E8F0" }
|
|
5216
|
+
},
|
|
5217
|
+
bottom: {
|
|
5218
|
+
style: "medium",
|
|
5219
|
+
color: { argb: "FFCBD5E1" }
|
|
5220
|
+
},
|
|
5221
|
+
left: {
|
|
5222
|
+
style: "thin",
|
|
5223
|
+
color: { argb: "FFE2E8F0" }
|
|
5224
|
+
},
|
|
5225
|
+
right: {
|
|
5226
|
+
style: "thin",
|
|
5227
|
+
color: { argb: "FFE2E8F0" }
|
|
5228
|
+
}
|
|
5229
|
+
};
|
|
5230
|
+
});
|
|
5231
|
+
for (const record of records) {
|
|
5232
|
+
const rowValues = {};
|
|
5233
|
+
for (const col of columns) {
|
|
5234
|
+
const val = record[col.key];
|
|
5235
|
+
rowValues[col.key] = formatExcelCellValue(val);
|
|
5236
|
+
}
|
|
5237
|
+
const row = worksheet.addRow(rowValues);
|
|
5238
|
+
row.height = 20;
|
|
5239
|
+
row.eachCell((cell) => {
|
|
5240
|
+
cell.alignment = { vertical: "middle" };
|
|
5241
|
+
cell.border = {
|
|
5242
|
+
top: {
|
|
5243
|
+
style: "thin",
|
|
5244
|
+
color: { argb: "FFF1F5F9" }
|
|
5245
|
+
},
|
|
5246
|
+
bottom: {
|
|
5247
|
+
style: "thin",
|
|
5248
|
+
color: { argb: "FFF1F5F9" }
|
|
5249
|
+
},
|
|
5250
|
+
left: {
|
|
5251
|
+
style: "thin",
|
|
5252
|
+
color: { argb: "FFF1F5F9" }
|
|
5253
|
+
},
|
|
5254
|
+
right: {
|
|
5255
|
+
style: "thin",
|
|
5256
|
+
color: { argb: "FFF1F5F9" }
|
|
5257
|
+
}
|
|
5258
|
+
};
|
|
5259
|
+
});
|
|
5260
|
+
}
|
|
5261
|
+
const arrayBuffer = await workbook.xlsx.writeBuffer();
|
|
5262
|
+
return new Uint8Array(arrayBuffer);
|
|
5263
|
+
}
|
|
5264
|
+
function formatHeaderLabel(key) {
|
|
5265
|
+
return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
5266
|
+
}
|
|
5267
|
+
function formatExcelCellValue(val) {
|
|
5268
|
+
if (val === void 0 || val === null) return "";
|
|
5269
|
+
if (typeof val === "number" || typeof val === "boolean") return val;
|
|
5270
|
+
if (val instanceof Date) return val;
|
|
5271
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
5272
|
+
return String(val);
|
|
5273
|
+
}
|
|
5274
|
+
//#endregion
|
|
5275
|
+
//#region src/plugins/transfer/serializers/index.ts
|
|
5276
|
+
async function serializeSource(records, options = {}) {
|
|
5277
|
+
const format = options.format || "json";
|
|
5278
|
+
const baseName = options.fileName || "export";
|
|
5279
|
+
switch (format) {
|
|
5280
|
+
case "excel": return {
|
|
5281
|
+
format: "excel",
|
|
5282
|
+
data: await serializeExcel(records, {
|
|
5283
|
+
sheetName: options.sheetName,
|
|
5284
|
+
columns: options.columns
|
|
5285
|
+
}),
|
|
5286
|
+
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
5287
|
+
fileName: baseName.endsWith(".xlsx") ? baseName : `${baseName}.xlsx`,
|
|
5288
|
+
totalRecords: records.length
|
|
5289
|
+
};
|
|
5290
|
+
case "csv": return {
|
|
5291
|
+
format: "csv",
|
|
5292
|
+
data: serializeCsv(records, { columns: options.columns }),
|
|
5293
|
+
mimeType: "text/csv; charset=utf-8",
|
|
5294
|
+
fileName: baseName.endsWith(".csv") ? baseName : `${baseName}.csv`,
|
|
5295
|
+
totalRecords: records.length
|
|
5296
|
+
};
|
|
5297
|
+
default: return {
|
|
5298
|
+
format: "json",
|
|
5299
|
+
data: serializeJson(records, { pretty: true }),
|
|
5300
|
+
mimeType: "application/json; charset=utf-8",
|
|
5301
|
+
fileName: baseName.endsWith(".json") ? baseName : `${baseName}.json`,
|
|
5302
|
+
totalRecords: records.length
|
|
5303
|
+
};
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
//#endregion
|
|
5307
|
+
//#region src/plugins/transfer/inspect.ts
|
|
5308
|
+
/**
|
|
5309
|
+
* Inspect an uploaded source file or payload to discover its schema,
|
|
5310
|
+
* infer column types, and generate auto-mapping suggestions against a target collection.
|
|
5311
|
+
*/
|
|
5312
|
+
async function inspectSource(input, options = {}) {
|
|
5313
|
+
const parsed = await parseSource(input, {
|
|
5314
|
+
format: options.format,
|
|
5315
|
+
fileName: options.fileName,
|
|
5316
|
+
sheetName: options.sheetName
|
|
5317
|
+
});
|
|
5318
|
+
const records = parsed.records;
|
|
5319
|
+
const totalRows = records.length;
|
|
5320
|
+
const previewLimit = options.previewLimit ?? 5;
|
|
5321
|
+
const previewRows = records.slice(0, previewLimit);
|
|
5322
|
+
const fieldKeys = /* @__PURE__ */ new Set();
|
|
5323
|
+
for (const row of records) for (const key of Object.keys(row)) fieldKeys.add(key);
|
|
5324
|
+
const sourceFields = [];
|
|
5325
|
+
for (const key of fieldKeys) {
|
|
5326
|
+
let nullCount = 0;
|
|
5327
|
+
const samples = [];
|
|
5328
|
+
const detectedTypes = [];
|
|
5329
|
+
for (const row of records) {
|
|
5330
|
+
const val = row[key];
|
|
5331
|
+
if (val === void 0 || val === null || val === "") nullCount++;
|
|
5332
|
+
else {
|
|
5333
|
+
if (samples.length < 5) samples.push(val);
|
|
5334
|
+
detectedTypes.push(inferValueType(val));
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
sourceFields.push({
|
|
5338
|
+
name: key,
|
|
5339
|
+
inferredType: resolveDominantType(detectedTypes),
|
|
5340
|
+
sampleValues: samples,
|
|
5341
|
+
nullCount,
|
|
5342
|
+
totalCount: totalRows
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
let targetSchema;
|
|
5346
|
+
let suggestedMapping;
|
|
5347
|
+
if (options.collectionConfig) {
|
|
5348
|
+
targetSchema = summarizeCollection(options.collectionConfig);
|
|
5349
|
+
suggestedMapping = generateSuggestedMapping(sourceFields.map((f) => f.name), targetSchema);
|
|
5350
|
+
}
|
|
5351
|
+
return {
|
|
5352
|
+
format: parsed.format,
|
|
5353
|
+
sheets: parsed.sheets,
|
|
5354
|
+
selectedSheet: parsed.selectedSheet,
|
|
5355
|
+
totalRows,
|
|
5356
|
+
sourceFields,
|
|
5357
|
+
targetSchema,
|
|
5358
|
+
suggestedMapping,
|
|
5359
|
+
previewRows
|
|
5360
|
+
};
|
|
5361
|
+
}
|
|
5362
|
+
/**
|
|
5363
|
+
* Summarize a CMS CollectionConfig into consumer-friendly schema details.
|
|
5364
|
+
*/
|
|
5365
|
+
function summarizeCollection(config) {
|
|
5366
|
+
return {
|
|
5367
|
+
slug: config.slug,
|
|
5368
|
+
label: config.label,
|
|
5369
|
+
singularLabel: config.singularLabel || config.label,
|
|
5370
|
+
description: config.description,
|
|
5371
|
+
taxonomies: config.taxonomies || [],
|
|
5372
|
+
fields: config.fields.map((f) => ({
|
|
5373
|
+
name: f.name,
|
|
5374
|
+
label: f.label || f.name,
|
|
5375
|
+
type: f.type,
|
|
5376
|
+
required: Boolean(f.required),
|
|
5377
|
+
unique: Boolean(f.unique),
|
|
5378
|
+
description: f.description,
|
|
5379
|
+
defaultValue: f.defaultValue,
|
|
5380
|
+
targetCollection: f.targetCollection,
|
|
5381
|
+
options: f.options
|
|
5382
|
+
}))
|
|
5383
|
+
};
|
|
5384
|
+
}
|
|
5385
|
+
/**
|
|
5386
|
+
* Generates intelligent auto-mapping suggestions matching source headers to CMS fields.
|
|
5387
|
+
*/
|
|
5388
|
+
function generateSuggestedMapping(sourceHeaders, targetSchema) {
|
|
5389
|
+
const suggestions = [];
|
|
5390
|
+
const matchedTargets = /* @__PURE__ */ new Set();
|
|
5391
|
+
for (const sourceHeader of sourceHeaders) {
|
|
5392
|
+
let bestMatch = null;
|
|
5393
|
+
for (const targetField of targetSchema.fields) {
|
|
5394
|
+
if (matchedTargets.has(targetField.name)) continue;
|
|
5395
|
+
const score = calculateMatchConfidence(sourceHeader, targetField);
|
|
5396
|
+
if (score >= .6 && (!bestMatch || score > bestMatch.confidence)) bestMatch = {
|
|
5397
|
+
field: targetField.name,
|
|
5398
|
+
confidence: score
|
|
5399
|
+
};
|
|
5400
|
+
}
|
|
5401
|
+
if (bestMatch) {
|
|
5402
|
+
matchedTargets.add(bestMatch.field);
|
|
5403
|
+
suggestions.push({
|
|
5404
|
+
sourceField: sourceHeader,
|
|
5405
|
+
targetField: bestMatch.field,
|
|
5406
|
+
confidence: Number(bestMatch.confidence.toFixed(2))
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
return suggestions;
|
|
5411
|
+
}
|
|
5412
|
+
function normalize(str) {
|
|
5413
|
+
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
5414
|
+
}
|
|
5415
|
+
/**
|
|
5416
|
+
* Common field synonyms dictionary for intuitive fuzzy matching.
|
|
5417
|
+
*/
|
|
5418
|
+
const SYNONYMS = {
|
|
5419
|
+
employeeNumber: [
|
|
5420
|
+
"empid",
|
|
5421
|
+
"employeeid",
|
|
5422
|
+
"empnumber",
|
|
5423
|
+
"staffid",
|
|
5424
|
+
"badgenumber",
|
|
5425
|
+
"staffno",
|
|
5426
|
+
"empno"
|
|
5427
|
+
],
|
|
5428
|
+
companyName: [
|
|
5429
|
+
"company",
|
|
5430
|
+
"organization",
|
|
5431
|
+
"employer",
|
|
5432
|
+
"businessname",
|
|
5433
|
+
"employername",
|
|
5434
|
+
"firm"
|
|
5435
|
+
],
|
|
5436
|
+
firstName: [
|
|
5437
|
+
"fname",
|
|
5438
|
+
"givenname",
|
|
5439
|
+
"first"
|
|
5440
|
+
],
|
|
5441
|
+
lastName: [
|
|
5442
|
+
"lname",
|
|
5443
|
+
"surname",
|
|
5444
|
+
"familyname",
|
|
5445
|
+
"last"
|
|
5446
|
+
],
|
|
5447
|
+
email: [
|
|
5448
|
+
"mail",
|
|
5449
|
+
"workemail",
|
|
5450
|
+
"primaryemail",
|
|
5451
|
+
"emailaddress"
|
|
5452
|
+
],
|
|
5453
|
+
phone: [
|
|
5454
|
+
"mobile",
|
|
5455
|
+
"cell",
|
|
5456
|
+
"telephone",
|
|
5457
|
+
"phonenumber",
|
|
5458
|
+
"contactnumber",
|
|
5459
|
+
"tel"
|
|
5460
|
+
],
|
|
5461
|
+
employerId: [
|
|
5462
|
+
"employer",
|
|
5463
|
+
"companyid",
|
|
5464
|
+
"company",
|
|
5465
|
+
"organizationid",
|
|
5466
|
+
"orgid"
|
|
5467
|
+
],
|
|
5468
|
+
hireDate: [
|
|
5469
|
+
"startdate",
|
|
5470
|
+
"joiningdate",
|
|
5471
|
+
"hired",
|
|
5472
|
+
"dateofjoining"
|
|
5473
|
+
],
|
|
5474
|
+
jobTitle: [
|
|
5475
|
+
"title",
|
|
5476
|
+
"position",
|
|
5477
|
+
"role",
|
|
5478
|
+
"designation"
|
|
5479
|
+
],
|
|
5480
|
+
employmentType: [
|
|
5481
|
+
"contracttype",
|
|
5482
|
+
"worktype",
|
|
5483
|
+
"jobtype"
|
|
5484
|
+
],
|
|
5485
|
+
salary: [
|
|
5486
|
+
"compensation",
|
|
5487
|
+
"wage",
|
|
5488
|
+
"pay",
|
|
5489
|
+
"rate",
|
|
5490
|
+
"annualsalary"
|
|
5491
|
+
],
|
|
5492
|
+
taxId: [
|
|
5493
|
+
"ein",
|
|
5494
|
+
"vat",
|
|
5495
|
+
"taxnumber",
|
|
5496
|
+
"tin"
|
|
5497
|
+
],
|
|
5498
|
+
status: ["state", "active"]
|
|
5499
|
+
};
|
|
5500
|
+
function calculateMatchConfidence(sourceHeader, targetField) {
|
|
5501
|
+
const normSource = normalize(sourceHeader);
|
|
5502
|
+
const normTargetName = normalize(targetField.name);
|
|
5503
|
+
const normTargetLabel = normalize(targetField.label);
|
|
5504
|
+
if (sourceHeader === targetField.name) return 1;
|
|
5505
|
+
if (normSource === normTargetName) return .95;
|
|
5506
|
+
if (sourceHeader.toLowerCase() === targetField.label.toLowerCase()) return .92;
|
|
5507
|
+
if (normSource === normTargetLabel) return .9;
|
|
5508
|
+
const synonyms = SYNONYMS[targetField.name];
|
|
5509
|
+
if (synonyms && synonyms.includes(normSource)) return .85;
|
|
5510
|
+
if (normSource.includes(normTargetName) || normTargetName.includes(normSource)) return .7;
|
|
5511
|
+
if (normSource.includes(normTargetLabel) || normTargetLabel.includes(normSource)) return .65;
|
|
5512
|
+
return 0;
|
|
5513
|
+
}
|
|
5514
|
+
function inferValueType(val) {
|
|
5515
|
+
if (typeof val === "boolean") return "boolean";
|
|
5516
|
+
if (typeof val === "number") return "number";
|
|
5517
|
+
if (Array.isArray(val)) return "array";
|
|
5518
|
+
if (val && typeof val === "object") return "object";
|
|
5519
|
+
if (typeof val === "string") {
|
|
5520
|
+
const s = val.trim();
|
|
5521
|
+
if (s.toLowerCase() === "true" || s.toLowerCase() === "false") return "boolean";
|
|
5522
|
+
if (/^-?\d+(\.\d+)?$/.test(s) && !/^0\d+/.test(s)) return "number";
|
|
5523
|
+
if (/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/.test(s)) {
|
|
5524
|
+
const parsed = Date.parse(s);
|
|
5525
|
+
if (!Number.isNaN(parsed)) return "date";
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
return "string";
|
|
5529
|
+
}
|
|
5530
|
+
function resolveDominantType(types) {
|
|
5531
|
+
if (types.length === 0) return "string";
|
|
5532
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5533
|
+
for (const t of types) counts.set(t, (counts.get(t) || 0) + 1);
|
|
5534
|
+
let dominant = "string";
|
|
5535
|
+
let maxCount = 0;
|
|
5536
|
+
for (const [t, count] of counts) if (count > maxCount) {
|
|
5537
|
+
maxCount = count;
|
|
5538
|
+
dominant = t;
|
|
5539
|
+
}
|
|
5540
|
+
return dominant;
|
|
5541
|
+
}
|
|
5542
|
+
//#endregion
|
|
5543
|
+
//#region src/plugins/transfer/mapping.ts
|
|
5544
|
+
/**
|
|
5545
|
+
* Apply a mapping definition to transform an external raw record into a CMS content payload.
|
|
5546
|
+
*/
|
|
5547
|
+
function mapSourceRecord(rawRecord, mapping) {
|
|
5548
|
+
const mappedData = {};
|
|
5549
|
+
const mappedTargetFields = /* @__PURE__ */ new Set();
|
|
5550
|
+
for (const rule of mapping.fields) {
|
|
5551
|
+
mappedTargetFields.add(rule.targetField);
|
|
5552
|
+
const sourceVal = rawRecord[rule.sourceField];
|
|
5553
|
+
const transformed = applyFieldTransform(rule, sourceVal, rawRecord);
|
|
5554
|
+
if (transformed !== void 0) mappedData[rule.targetField] = transformed;
|
|
5555
|
+
}
|
|
5556
|
+
if (mapping.options?.ignoreUnmappedFields === false) {
|
|
5557
|
+
for (const [key, val] of Object.entries(rawRecord)) if (!mapping.fields.some((r) => r.sourceField === key)) mappedData[key] = val;
|
|
5558
|
+
}
|
|
5559
|
+
let title = typeof mappedData.title === "string" ? mappedData.title : void 0;
|
|
5560
|
+
if (!title) {
|
|
5561
|
+
if (typeof mappedData.companyName === "string") title = mappedData.companyName;
|
|
5562
|
+
else if (typeof mappedData.name === "string") title = mappedData.name;
|
|
5563
|
+
else if (typeof mappedData.firstName === "string" || typeof mappedData.lastName === "string") {
|
|
5564
|
+
const parts = [mappedData.firstName, mappedData.lastName].filter(Boolean);
|
|
5565
|
+
if (parts.length > 0) title = parts.join(" ");
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
let slug = typeof mappedData.slug === "string" ? mappedData.slug : void 0;
|
|
5569
|
+
if (!slug && mapping.options?.autoGenerateSlug !== false && title) slug = slugify(title);
|
|
5570
|
+
let status = mapping.options?.defaultStatus;
|
|
5571
|
+
if (mapping.options?.statusField && mappedData[mapping.options.statusField]) status = String(mappedData[mapping.options.statusField]);
|
|
5572
|
+
return {
|
|
5573
|
+
data: mappedData,
|
|
5574
|
+
title,
|
|
5575
|
+
slug,
|
|
5576
|
+
status
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
/**
|
|
5580
|
+
* Applies transform preset or custom function to a field value.
|
|
5581
|
+
*/
|
|
5582
|
+
function applyFieldTransform(rule, value, rawRecord) {
|
|
5583
|
+
let val = value;
|
|
5584
|
+
if (typeof rule.transform === "function") try {
|
|
5585
|
+
val = rule.transform(val, rawRecord);
|
|
5586
|
+
} catch (err) {
|
|
5587
|
+
throw new Error(`Transform error on field '${rule.sourceField}': ${err instanceof Error ? err.message : String(err)}`);
|
|
5588
|
+
}
|
|
5589
|
+
else if (rule.transform && typeof rule.transform === "string") val = applyPresetTransform(rule.transform, val);
|
|
5590
|
+
if (val === void 0 || val === null || val === "") {
|
|
5591
|
+
if (rule.defaultValue !== void 0) val = rule.defaultValue;
|
|
5592
|
+
}
|
|
5593
|
+
return val;
|
|
5594
|
+
}
|
|
5595
|
+
function applyPresetTransform(preset, val) {
|
|
5596
|
+
if (val === void 0 || val === null) return val;
|
|
5597
|
+
switch (preset) {
|
|
5598
|
+
case "trim": return typeof val === "string" ? val.trim() : val;
|
|
5599
|
+
case "lowercase": return typeof val === "string" ? val.toLowerCase() : val;
|
|
5600
|
+
case "uppercase": return typeof val === "string" ? val.toUpperCase() : val;
|
|
5601
|
+
case "number": {
|
|
5602
|
+
if (typeof val === "number") return val;
|
|
5603
|
+
const parsed = Number(val);
|
|
5604
|
+
return Number.isNaN(parsed) ? val : parsed;
|
|
5605
|
+
}
|
|
5606
|
+
case "boolean": {
|
|
5607
|
+
if (typeof val === "boolean") return val;
|
|
5608
|
+
const s = String(val).toLowerCase().trim();
|
|
5609
|
+
if (s === "true" || s === "1" || s === "yes") return true;
|
|
5610
|
+
if (s === "false" || s === "0" || s === "no") return false;
|
|
5611
|
+
return Boolean(val);
|
|
5612
|
+
}
|
|
5613
|
+
case "date": {
|
|
5614
|
+
if (val instanceof Date) return val.toISOString();
|
|
5615
|
+
const s = String(val).trim();
|
|
5616
|
+
const parsed = Date.parse(s);
|
|
5617
|
+
return Number.isNaN(parsed) ? s : new Date(parsed).toISOString();
|
|
5618
|
+
}
|
|
5619
|
+
case "json":
|
|
5620
|
+
if (typeof val === "object") return val;
|
|
5621
|
+
try {
|
|
5622
|
+
return JSON.parse(String(val));
|
|
5623
|
+
} catch {
|
|
5624
|
+
return val;
|
|
5625
|
+
}
|
|
5626
|
+
case "slug": return slugify(String(val));
|
|
5627
|
+
case "split_comma":
|
|
5628
|
+
if (Array.isArray(val)) return val;
|
|
5629
|
+
return String(val).split(",").map((s) => s.trim()).filter(Boolean);
|
|
5630
|
+
default: return val;
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
/**
|
|
5634
|
+
* Reverse mapping for export: transforms a CMS ContentItem into an external export record.
|
|
5635
|
+
*/
|
|
5636
|
+
function mapCmsItemForExport(item, mapping, options = {}) {
|
|
5637
|
+
const exportRecord = {};
|
|
5638
|
+
if (mapping && mapping.fields.length > 0) {
|
|
5639
|
+
for (const rule of mapping.fields) {
|
|
5640
|
+
const val = item.data[rule.targetField] !== void 0 ? item.data[rule.targetField] : item[rule.targetField];
|
|
5641
|
+
exportRecord[rule.sourceField] = val !== void 0 ? val : rule.defaultValue ?? "";
|
|
5642
|
+
}
|
|
5643
|
+
return exportRecord;
|
|
5644
|
+
}
|
|
5645
|
+
if (options.includeId !== false) exportRecord.id = item.id;
|
|
5646
|
+
if (item.title) exportRecord.title = item.title;
|
|
5647
|
+
if (item.slug) exportRecord.slug = item.slug;
|
|
5648
|
+
if (item.status) exportRecord.status = item.status;
|
|
5649
|
+
for (const [k, v] of Object.entries(item.data)) exportRecord[k] = v;
|
|
5650
|
+
if (options.includeTimestamps) {
|
|
5651
|
+
exportRecord.createdAt = item.createdAt;
|
|
5652
|
+
exportRecord.updatedAt = item.updatedAt;
|
|
5653
|
+
}
|
|
5654
|
+
return exportRecord;
|
|
5655
|
+
}
|
|
5656
|
+
//#endregion
|
|
5657
|
+
//#region src/plugins/transfer/validator.ts
|
|
5658
|
+
/**
|
|
5659
|
+
* Validates a batch of source records against a target CMS collection configuration
|
|
5660
|
+
* and live database constraints.
|
|
5661
|
+
*/
|
|
5662
|
+
async function validateTransferBatch(rawRecords, mapping, collectionConfig, options = {}) {
|
|
5663
|
+
const errors = [];
|
|
5664
|
+
const previewRows = [];
|
|
5665
|
+
const previewLimit = options.previewLimit ?? 10;
|
|
5666
|
+
const onDuplicate = options.onDuplicate ?? mapping.options?.onDuplicate ?? "error";
|
|
5667
|
+
const uniqueFields = collectionConfig.fields.filter((f) => Boolean(f.unique) || f.name === "slug");
|
|
5668
|
+
const relationshipFields = collectionConfig.fields.filter((f) => f.type === "relationship" && Boolean(f.targetCollection));
|
|
5669
|
+
const seenUniqueValues = /* @__PURE__ */ new Map();
|
|
5670
|
+
for (const uf of uniqueFields) seenUniqueValues.set(uf.name, /* @__PURE__ */ new Map());
|
|
5671
|
+
let validCount = 0;
|
|
5672
|
+
for (let i = 0; i < rawRecords.length; i++) {
|
|
5673
|
+
const rowNumber = i + 1;
|
|
5674
|
+
const raw = rawRecords[i];
|
|
5675
|
+
const rowErrors = [];
|
|
5676
|
+
let mappedResult;
|
|
5677
|
+
try {
|
|
5678
|
+
mappedResult = mapSourceRecord(raw, mapping);
|
|
5679
|
+
} catch (err) {
|
|
5680
|
+
const errDetail = {
|
|
5681
|
+
rowNumber,
|
|
5682
|
+
code: "TRANSFORM_ERROR",
|
|
5683
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
5684
|
+
};
|
|
5685
|
+
rowErrors.push(errDetail);
|
|
5686
|
+
errors.push(errDetail);
|
|
5687
|
+
if (previewRows.length < previewLimit) previewRows.push({
|
|
5688
|
+
rowNumber,
|
|
5689
|
+
raw,
|
|
5690
|
+
mapped: {},
|
|
5691
|
+
valid: false,
|
|
5692
|
+
errors: rowErrors
|
|
5693
|
+
});
|
|
5694
|
+
continue;
|
|
5695
|
+
}
|
|
5696
|
+
const data = mappedResult.data;
|
|
5697
|
+
for (const field of collectionConfig.fields) validateField(field, data, rowNumber, rowErrors);
|
|
5698
|
+
for (const rule of mapping.fields) if (rule.required && !collectionConfig.fields.some((f) => f.name === rule.targetField)) {
|
|
5699
|
+
const val = data[rule.targetField];
|
|
5700
|
+
if (val === void 0 || val === null || val === "") rowErrors.push({
|
|
5701
|
+
rowNumber,
|
|
5702
|
+
field: rule.targetField,
|
|
5703
|
+
value: val,
|
|
5704
|
+
code: "REQUIRED_FIELD_MISSING",
|
|
5705
|
+
reason: `Field '${rule.targetField}' is required by mapping definition.`
|
|
5706
|
+
});
|
|
5707
|
+
}
|
|
5708
|
+
for (const uf of uniqueFields) {
|
|
5709
|
+
const val = data[uf.name] ?? (uf.name === "slug" ? mappedResult.slug : void 0);
|
|
5710
|
+
if (val !== void 0 && val !== null && val !== "") {
|
|
5711
|
+
const seenMap = seenUniqueValues.get(uf.name);
|
|
5712
|
+
if (seenMap.has(val)) {
|
|
5713
|
+
const prevRow = seenMap.get(val);
|
|
5714
|
+
rowErrors.push({
|
|
5715
|
+
rowNumber,
|
|
5716
|
+
field: uf.name,
|
|
5717
|
+
value: val,
|
|
5718
|
+
code: "CONSTRAINT_UNIQUE_VIOLATION",
|
|
5719
|
+
reason: `${uf.label || uf.name} value '${val}' is duplicated in row ${prevRow} and row ${rowNumber}.`
|
|
5720
|
+
});
|
|
5721
|
+
} else seenMap.set(val, rowNumber);
|
|
5722
|
+
if (options.storage && onDuplicate === "error") {
|
|
5723
|
+
if (await checkExistingUnique(options.storage, collectionConfig.slug, uf.name, val)) rowErrors.push({
|
|
5724
|
+
rowNumber,
|
|
5725
|
+
field: uf.name,
|
|
5726
|
+
value: val,
|
|
5727
|
+
code: "CONSTRAINT_UNIQUE_VIOLATION",
|
|
5728
|
+
reason: `${uf.label || uf.name} value '${val}' already exists in database.`
|
|
5729
|
+
});
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
if (options.storage) for (const rf of relationshipFields) {
|
|
5734
|
+
const targetColl = rf.targetCollection;
|
|
5735
|
+
const refVal = data[rf.name];
|
|
5736
|
+
if (refVal !== void 0 && refVal !== null && refVal !== "") {
|
|
5737
|
+
if (!await checkRelationshipExists(options.storage, targetColl, refVal)) rowErrors.push({
|
|
5738
|
+
rowNumber,
|
|
5739
|
+
field: rf.name,
|
|
5740
|
+
value: refVal,
|
|
5741
|
+
code: "CONSTRAINT_FOREIGN_KEY_VIOLATION",
|
|
5742
|
+
reason: `Referenced ${rf.label || rf.name} with identifier '${refVal}' does not exist in collection '${targetColl}'.`
|
|
5743
|
+
});
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
if (rowErrors.length === 0) validCount++;
|
|
5747
|
+
else errors.push(...rowErrors);
|
|
5748
|
+
if (previewRows.length < previewLimit) previewRows.push({
|
|
5749
|
+
rowNumber,
|
|
5750
|
+
raw,
|
|
5751
|
+
mapped: data,
|
|
5752
|
+
valid: rowErrors.length === 0,
|
|
5753
|
+
errors: rowErrors.length > 0 ? rowErrors : void 0
|
|
5754
|
+
});
|
|
5755
|
+
}
|
|
5756
|
+
return {
|
|
5757
|
+
valid: errors.length === 0,
|
|
5758
|
+
totalRows: rawRecords.length,
|
|
5759
|
+
validCount,
|
|
5760
|
+
errorCount: errors.length,
|
|
5761
|
+
errors,
|
|
5762
|
+
previewRows
|
|
5763
|
+
};
|
|
5764
|
+
}
|
|
5765
|
+
/**
|
|
5766
|
+
* Validates a single field against its FieldDefinition.
|
|
5767
|
+
*/
|
|
5768
|
+
function validateField(field, data, rowNumber, errors) {
|
|
5769
|
+
const val = data[field.name];
|
|
5770
|
+
const label = field.label || field.name;
|
|
5771
|
+
if (field.required && (val === void 0 || val === null || val === "")) {
|
|
5772
|
+
errors.push({
|
|
5773
|
+
rowNumber,
|
|
5774
|
+
field: field.name,
|
|
5775
|
+
value: val,
|
|
5776
|
+
code: "REQUIRED_FIELD_MISSING",
|
|
5777
|
+
reason: `${label} is required.`
|
|
5778
|
+
});
|
|
5779
|
+
return;
|
|
5780
|
+
}
|
|
5781
|
+
if (val === void 0 || val === null || val === "") return;
|
|
5782
|
+
switch (field.type) {
|
|
5783
|
+
case "number": {
|
|
5784
|
+
const num = typeof val === "number" ? val : Number(val);
|
|
5785
|
+
if (Number.isNaN(num)) errors.push({
|
|
5786
|
+
rowNumber,
|
|
5787
|
+
field: field.name,
|
|
5788
|
+
value: val,
|
|
5789
|
+
code: "INVALID_DATA_TYPE",
|
|
5790
|
+
reason: `${label} must be a valid number, received '${val}'.`
|
|
5791
|
+
});
|
|
5792
|
+
else {
|
|
5793
|
+
data[field.name] = num;
|
|
5794
|
+
const min = field.min;
|
|
5795
|
+
const max = field.max;
|
|
5796
|
+
if (min !== void 0 && num < min) errors.push({
|
|
5797
|
+
rowNumber,
|
|
5798
|
+
field: field.name,
|
|
5799
|
+
value: val,
|
|
5800
|
+
code: "VALIDATION_RULE_FAILED",
|
|
5801
|
+
reason: `${label} must be greater than or equal to ${min}.`
|
|
5802
|
+
});
|
|
5803
|
+
if (max !== void 0 && num > max) errors.push({
|
|
5804
|
+
rowNumber,
|
|
5805
|
+
field: field.name,
|
|
5806
|
+
value: val,
|
|
5807
|
+
code: "VALIDATION_RULE_FAILED",
|
|
5808
|
+
reason: `${label} must be less than or equal to ${max}.`
|
|
5809
|
+
});
|
|
5810
|
+
}
|
|
5811
|
+
break;
|
|
5812
|
+
}
|
|
5813
|
+
case "boolean":
|
|
5814
|
+
if (typeof val !== "boolean") {
|
|
5815
|
+
const s = String(val).toLowerCase().trim();
|
|
5816
|
+
if (s === "true" || s === "1" || s === "yes") data[field.name] = true;
|
|
5817
|
+
else if (s === "false" || s === "0" || s === "no") data[field.name] = false;
|
|
5818
|
+
else errors.push({
|
|
5819
|
+
rowNumber,
|
|
5820
|
+
field: field.name,
|
|
5821
|
+
value: val,
|
|
5822
|
+
code: "INVALID_DATA_TYPE",
|
|
5823
|
+
reason: `${label} must be a boolean (true/false), received '${val}'.`
|
|
5824
|
+
});
|
|
5825
|
+
}
|
|
5826
|
+
break;
|
|
5827
|
+
case "date": {
|
|
5828
|
+
const parsed = Date.parse(String(val));
|
|
5829
|
+
if (Number.isNaN(parsed)) errors.push({
|
|
5830
|
+
rowNumber,
|
|
5831
|
+
field: field.name,
|
|
5832
|
+
value: val,
|
|
5833
|
+
code: "INVALID_DATA_TYPE",
|
|
5834
|
+
reason: `${label} must be a valid date, received '${val}'.`
|
|
5835
|
+
});
|
|
5836
|
+
break;
|
|
5837
|
+
}
|
|
5838
|
+
case "select": {
|
|
5839
|
+
const options = field.options;
|
|
5840
|
+
if (options && options.length > 0) {
|
|
5841
|
+
const allowedValues = options.map((opt) => typeof opt === "object" ? String(opt.value) : String(opt));
|
|
5842
|
+
if (!allowedValues.includes(String(val))) errors.push({
|
|
5843
|
+
rowNumber,
|
|
5844
|
+
field: field.name,
|
|
5845
|
+
value: val,
|
|
5846
|
+
code: "INVALID_SELECT_OPTION",
|
|
5847
|
+
reason: `${label} value '${val}' is not a valid option (allowed: ${allowedValues.join(", ")}).`
|
|
5848
|
+
});
|
|
5849
|
+
}
|
|
5850
|
+
break;
|
|
5851
|
+
}
|
|
5852
|
+
case "json":
|
|
5853
|
+
if (typeof val === "string") try {
|
|
5854
|
+
data[field.name] = JSON.parse(val);
|
|
5855
|
+
} catch {
|
|
5856
|
+
errors.push({
|
|
5857
|
+
rowNumber,
|
|
5858
|
+
field: field.name,
|
|
5859
|
+
value: val,
|
|
5860
|
+
code: "INVALID_DATA_TYPE",
|
|
5861
|
+
reason: `${label} must be a valid JSON structure.`
|
|
5862
|
+
});
|
|
5863
|
+
}
|
|
5864
|
+
break;
|
|
5865
|
+
}
|
|
5866
|
+
if (typeof field.validate === "function") try {
|
|
5867
|
+
const res = field.validate(val, data);
|
|
5868
|
+
if (typeof res === "string") errors.push({
|
|
5869
|
+
rowNumber,
|
|
5870
|
+
field: field.name,
|
|
5871
|
+
value: val,
|
|
5872
|
+
code: "VALIDATION_RULE_FAILED",
|
|
5873
|
+
reason: res
|
|
5874
|
+
});
|
|
5875
|
+
else if (res === false) errors.push({
|
|
5876
|
+
rowNumber,
|
|
5877
|
+
field: field.name,
|
|
5878
|
+
value: val,
|
|
5879
|
+
code: "VALIDATION_RULE_FAILED",
|
|
5880
|
+
reason: `${label} failed custom validation constraint.`
|
|
5881
|
+
});
|
|
5882
|
+
} catch (err) {
|
|
5883
|
+
errors.push({
|
|
5884
|
+
rowNumber,
|
|
5885
|
+
field: field.name,
|
|
5886
|
+
value: val,
|
|
5887
|
+
code: "VALIDATION_RULE_FAILED",
|
|
5888
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
5889
|
+
});
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
async function checkExistingUnique(storage, collectionSlug, fieldName, value) {
|
|
5893
|
+
if (fieldName === "slug") return await storage.getContentBySlug(collectionSlug, String(value)) !== null;
|
|
5894
|
+
return (await storage.findContent(collectionSlug, {
|
|
5895
|
+
where: { [fieldName]: value },
|
|
5896
|
+
limit: 1
|
|
5897
|
+
})).items.length > 0;
|
|
5898
|
+
}
|
|
5899
|
+
async function checkRelationshipExists(storage, targetCollection, refId) {
|
|
5900
|
+
const idStr = String(refId);
|
|
5901
|
+
if (await storage.getContent(targetCollection, idStr) !== null) return true;
|
|
5902
|
+
return await storage.getContentBySlug(targetCollection, idStr) !== null;
|
|
5903
|
+
}
|
|
5904
|
+
//#endregion
|
|
5905
|
+
//#region src/plugins/transfer/presets/hrms.ts
|
|
5906
|
+
/**
|
|
5907
|
+
* Preconfigured mapping definition for importing and exporting HRMS Employers.
|
|
5908
|
+
*/
|
|
5909
|
+
const HRMS_EMPLOYER_TRANSFER_PRESET = {
|
|
5910
|
+
id: "hrms_employers_preset",
|
|
5911
|
+
name: "HRMS Employers Standard Mapping",
|
|
5912
|
+
collectionSlug: "employers",
|
|
5913
|
+
fields: [
|
|
5914
|
+
{
|
|
5915
|
+
sourceField: "Company Name",
|
|
5916
|
+
targetField: "companyName",
|
|
5917
|
+
required: true
|
|
5918
|
+
},
|
|
5919
|
+
{
|
|
5920
|
+
sourceField: "Legal Name",
|
|
5921
|
+
targetField: "legalName"
|
|
5922
|
+
},
|
|
5923
|
+
{
|
|
5924
|
+
sourceField: "Tax ID",
|
|
5925
|
+
targetField: "taxId"
|
|
5926
|
+
},
|
|
5927
|
+
{
|
|
5928
|
+
sourceField: "Primary Email",
|
|
5929
|
+
targetField: "email",
|
|
5930
|
+
transform: "trim"
|
|
5931
|
+
},
|
|
5932
|
+
{
|
|
5933
|
+
sourceField: "Phone",
|
|
5934
|
+
targetField: "phone",
|
|
5935
|
+
transform: "trim"
|
|
5936
|
+
},
|
|
5937
|
+
{
|
|
5938
|
+
sourceField: "Website",
|
|
5939
|
+
targetField: "website"
|
|
5940
|
+
},
|
|
5941
|
+
{
|
|
5942
|
+
sourceField: "Timezone",
|
|
5943
|
+
targetField: "timezone",
|
|
5944
|
+
defaultValue: "UTC"
|
|
5945
|
+
},
|
|
5946
|
+
{
|
|
5947
|
+
sourceField: "Status",
|
|
5948
|
+
targetField: "status",
|
|
5949
|
+
defaultValue: "active"
|
|
5950
|
+
}
|
|
5951
|
+
],
|
|
5952
|
+
options: {
|
|
5953
|
+
autoGenerateSlug: true,
|
|
5954
|
+
defaultStatus: "published",
|
|
5955
|
+
onDuplicate: "update",
|
|
5956
|
+
uniqueIdentifierField: "taxId"
|
|
5957
|
+
}
|
|
5958
|
+
};
|
|
5959
|
+
/**
|
|
5960
|
+
* Preconfigured mapping definition for importing and exporting HRMS Employees.
|
|
5961
|
+
*/
|
|
5962
|
+
const HRMS_EMPLOYEE_TRANSFER_PRESET = {
|
|
5963
|
+
id: "hrms_employees_preset",
|
|
5964
|
+
name: "HRMS Employees Standard Mapping",
|
|
5965
|
+
collectionSlug: "employees",
|
|
5966
|
+
fields: [
|
|
5967
|
+
{
|
|
5968
|
+
sourceField: "Employer ID",
|
|
5969
|
+
targetField: "employerId",
|
|
5970
|
+
required: true
|
|
5971
|
+
},
|
|
5972
|
+
{
|
|
5973
|
+
sourceField: "Employee Number",
|
|
5974
|
+
targetField: "employeeNumber",
|
|
5975
|
+
required: true,
|
|
5976
|
+
transform: "trim"
|
|
5977
|
+
},
|
|
5978
|
+
{
|
|
5979
|
+
sourceField: "First Name",
|
|
5980
|
+
targetField: "firstName",
|
|
5981
|
+
required: true,
|
|
5982
|
+
transform: "trim"
|
|
5983
|
+
},
|
|
5984
|
+
{
|
|
5985
|
+
sourceField: "Last Name",
|
|
5986
|
+
targetField: "lastName",
|
|
5987
|
+
required: true,
|
|
5988
|
+
transform: "trim"
|
|
5989
|
+
},
|
|
5990
|
+
{
|
|
5991
|
+
sourceField: "Work Email",
|
|
5992
|
+
targetField: "email",
|
|
5993
|
+
required: true,
|
|
5994
|
+
transform: "lowercase"
|
|
5995
|
+
},
|
|
5996
|
+
{
|
|
5997
|
+
sourceField: "Phone",
|
|
5998
|
+
targetField: "phone",
|
|
5999
|
+
transform: "trim"
|
|
6000
|
+
},
|
|
6001
|
+
{
|
|
6002
|
+
sourceField: "Job Title",
|
|
6003
|
+
targetField: "jobTitle"
|
|
6004
|
+
},
|
|
6005
|
+
{
|
|
6006
|
+
sourceField: "Employment Type",
|
|
6007
|
+
targetField: "employmentType",
|
|
6008
|
+
defaultValue: "full_time"
|
|
6009
|
+
},
|
|
6010
|
+
{
|
|
6011
|
+
sourceField: "Hire Date",
|
|
6012
|
+
targetField: "hireDate",
|
|
6013
|
+
required: true,
|
|
6014
|
+
transform: "date"
|
|
6015
|
+
},
|
|
6016
|
+
{
|
|
6017
|
+
sourceField: "Status",
|
|
6018
|
+
targetField: "status",
|
|
6019
|
+
defaultValue: "active"
|
|
6020
|
+
}
|
|
6021
|
+
],
|
|
6022
|
+
options: {
|
|
6023
|
+
autoGenerateSlug: true,
|
|
6024
|
+
defaultStatus: "published",
|
|
6025
|
+
onDuplicate: "error",
|
|
6026
|
+
uniqueIdentifierField: "employeeNumber"
|
|
6027
|
+
}
|
|
6028
|
+
};
|
|
6029
|
+
//#endregion
|
|
6030
|
+
//#region src/plugins/transfer/service.ts
|
|
6031
|
+
var TransferService = class {
|
|
6032
|
+
engine;
|
|
6033
|
+
options;
|
|
6034
|
+
presets = /* @__PURE__ */ new Map();
|
|
6035
|
+
constructor(engine, options = {}) {
|
|
6036
|
+
this.engine = engine;
|
|
6037
|
+
this.options = options;
|
|
6038
|
+
this.registerPreset(HRMS_EMPLOYER_TRANSFER_PRESET);
|
|
6039
|
+
this.registerPreset(HRMS_EMPLOYEE_TRANSFER_PRESET);
|
|
6040
|
+
}
|
|
6041
|
+
/**
|
|
6042
|
+
* List all registered collections across all active plugins in the engine.
|
|
6043
|
+
*/
|
|
6044
|
+
listCollections() {
|
|
6045
|
+
return this.engine.getCollections().map(summarizeCollection);
|
|
6046
|
+
}
|
|
6047
|
+
/**
|
|
6048
|
+
* Get the collection schema summary for a specific collection slug.
|
|
6049
|
+
*/
|
|
6050
|
+
getCollectionSchema(collectionSlug) {
|
|
6051
|
+
return summarizeCollection(this.getCollectionConfigOrThrow(collectionSlug));
|
|
6052
|
+
}
|
|
6053
|
+
/**
|
|
6054
|
+
* Register a reusable data mapping preset.
|
|
6055
|
+
*/
|
|
6056
|
+
registerPreset(preset) {
|
|
6057
|
+
const id = preset.id || `${preset.collectionSlug}_${preset.name || "preset"}`;
|
|
6058
|
+
this.presets.set(id, {
|
|
6059
|
+
...preset,
|
|
6060
|
+
id
|
|
6061
|
+
});
|
|
6062
|
+
}
|
|
6063
|
+
/**
|
|
6064
|
+
* Get a registered preset by ID.
|
|
6065
|
+
*/
|
|
6066
|
+
getPreset(id) {
|
|
6067
|
+
return this.presets.get(id);
|
|
6068
|
+
}
|
|
6069
|
+
/**
|
|
6070
|
+
* Get all registered presets, optionally filtered by target collection.
|
|
6071
|
+
*/
|
|
6072
|
+
getPresets(collectionSlug) {
|
|
6073
|
+
const all = Array.from(this.presets.values());
|
|
6074
|
+
if (collectionSlug) return all.filter((p) => p.collectionSlug === collectionSlug);
|
|
6075
|
+
return all;
|
|
6076
|
+
}
|
|
6077
|
+
/**
|
|
6078
|
+
* Generate a 1:1 default mapping template for any collection.
|
|
6079
|
+
*/
|
|
6080
|
+
getMappingTemplate(collectionSlug) {
|
|
6081
|
+
const config = this.getCollectionConfigOrThrow(collectionSlug);
|
|
6082
|
+
return {
|
|
6083
|
+
id: `${collectionSlug}_default_template`,
|
|
6084
|
+
name: `${config.label} Default Template`,
|
|
6085
|
+
collectionSlug,
|
|
6086
|
+
fields: config.fields.map((f) => ({
|
|
6087
|
+
sourceField: f.name,
|
|
6088
|
+
targetField: f.name,
|
|
6089
|
+
required: Boolean(f.required),
|
|
6090
|
+
defaultValue: f.defaultValue
|
|
6091
|
+
})),
|
|
6092
|
+
options: {
|
|
6093
|
+
autoGenerateSlug: true,
|
|
6094
|
+
defaultStatus: config.draftable ? "draft" : "published",
|
|
6095
|
+
onDuplicate: "error"
|
|
6096
|
+
}
|
|
6097
|
+
};
|
|
6098
|
+
}
|
|
6099
|
+
/**
|
|
6100
|
+
* Inspect any source payload or file (JSON, Excel, CSV) to extract headers,
|
|
6101
|
+
* infer data types, and generate auto-mapping suggestions against a target collection.
|
|
6102
|
+
*/
|
|
6103
|
+
async inspectSource(input, options = {}) {
|
|
6104
|
+
const config = options.collectionSlug ? this.engine.getCollectionConfig(options.collectionSlug) : void 0;
|
|
6105
|
+
return inspectSource(input, {
|
|
6106
|
+
...options,
|
|
6107
|
+
collectionConfig: config
|
|
6108
|
+
});
|
|
6109
|
+
}
|
|
6110
|
+
/**
|
|
6111
|
+
* Dry-run validation of a source batch against a target collection's schema
|
|
6112
|
+
* and live database constraints without committing any changes.
|
|
6113
|
+
*/
|
|
6114
|
+
async validateImport(collectionSlug, input, mapping, options = {}) {
|
|
6115
|
+
const config = this.getCollectionConfigOrThrow(collectionSlug);
|
|
6116
|
+
const parsed = await parseSource(input, {
|
|
6117
|
+
format: options.format,
|
|
6118
|
+
fileName: options.fileName,
|
|
6119
|
+
sheetName: options.sheetName
|
|
6120
|
+
});
|
|
6121
|
+
const activeMapping = mapping || this.getMappingTemplate(collectionSlug);
|
|
6122
|
+
return validateTransferBatch(parsed.records, activeMapping, config, {
|
|
6123
|
+
storage: this.engine.storage,
|
|
6124
|
+
onDuplicate: options.onDuplicate,
|
|
6125
|
+
previewLimit: options.previewLimit
|
|
6126
|
+
});
|
|
6127
|
+
}
|
|
6128
|
+
/**
|
|
6129
|
+
* Execute an import batch from raw data or an external file (JSON, Excel, CSV)
|
|
6130
|
+
* into a target CMS collection.
|
|
6131
|
+
*/
|
|
6132
|
+
async importData(collectionSlug, input, mapping, options = {}) {
|
|
6133
|
+
const config = this.getCollectionConfigOrThrow(collectionSlug);
|
|
6134
|
+
const parsed = await parseSource(input, {
|
|
6135
|
+
format: options.format,
|
|
6136
|
+
fileName: options.fileName,
|
|
6137
|
+
sheetName: options.sheetName
|
|
6138
|
+
});
|
|
6139
|
+
const activeMapping = mapping || this.getMappingTemplate(collectionSlug);
|
|
6140
|
+
const rawRecords = parsed.records;
|
|
6141
|
+
const onDuplicate = options.onDuplicate ?? activeMapping.options?.onDuplicate ?? "error";
|
|
6142
|
+
const abortOnError = options.abortOnError ?? activeMapping.options?.abortOnError ?? false;
|
|
6143
|
+
const validation = await validateTransferBatch(rawRecords, activeMapping, config, {
|
|
6144
|
+
storage: this.engine.storage,
|
|
6145
|
+
onDuplicate
|
|
6146
|
+
});
|
|
6147
|
+
if (options.dryRun) return {
|
|
6148
|
+
success: validation.valid,
|
|
6149
|
+
collectionSlug,
|
|
6150
|
+
totalRows: rawRecords.length,
|
|
6151
|
+
importedCount: validation.validCount,
|
|
6152
|
+
updatedCount: 0,
|
|
6153
|
+
skippedCount: 0,
|
|
6154
|
+
failedCount: validation.errorCount,
|
|
6155
|
+
createdIds: [],
|
|
6156
|
+
updatedIds: [],
|
|
6157
|
+
errors: validation.errors
|
|
6158
|
+
};
|
|
6159
|
+
if (abortOnError && !validation.valid) return {
|
|
6160
|
+
success: false,
|
|
6161
|
+
collectionSlug,
|
|
6162
|
+
totalRows: rawRecords.length,
|
|
6163
|
+
importedCount: 0,
|
|
6164
|
+
updatedCount: 0,
|
|
6165
|
+
skippedCount: 0,
|
|
6166
|
+
failedCount: validation.errorCount,
|
|
6167
|
+
createdIds: [],
|
|
6168
|
+
updatedIds: [],
|
|
6169
|
+
errors: validation.errors
|
|
6170
|
+
};
|
|
6171
|
+
await this.engine.hooks.doAction("cms.transfer_import_started", {
|
|
6172
|
+
collectionSlug,
|
|
6173
|
+
totalRows: rawRecords.length
|
|
6174
|
+
});
|
|
6175
|
+
const createdIds = [];
|
|
6176
|
+
const updatedIds = [];
|
|
6177
|
+
const errors = [...validation.errors];
|
|
6178
|
+
let importedCount = 0;
|
|
6179
|
+
let updatedCount = 0;
|
|
6180
|
+
let skippedCount = 0;
|
|
6181
|
+
const rowErrorsMap = /* @__PURE__ */ new Map();
|
|
6182
|
+
for (const err of validation.errors) {
|
|
6183
|
+
const list = rowErrorsMap.get(err.rowNumber) || [];
|
|
6184
|
+
list.push(err);
|
|
6185
|
+
rowErrorsMap.set(err.rowNumber, list);
|
|
6186
|
+
}
|
|
6187
|
+
const collService = this.engine.collection(collectionSlug);
|
|
6188
|
+
for (let i = 0; i < rawRecords.length; i++) {
|
|
6189
|
+
const rowNumber = i + 1;
|
|
6190
|
+
const rowErrors = rowErrorsMap.get(rowNumber);
|
|
6191
|
+
if (rowErrors && rowErrors.length > 0) {
|
|
6192
|
+
if (rowErrors.some((e) => e.code !== "CONSTRAINT_UNIQUE_VIOLATION" || onDuplicate === "error")) continue;
|
|
6193
|
+
}
|
|
6194
|
+
const raw = rawRecords[i];
|
|
6195
|
+
let mapped;
|
|
6196
|
+
try {
|
|
6197
|
+
mapped = mapSourceRecord(raw, activeMapping);
|
|
6198
|
+
} catch (err) {
|
|
6199
|
+
errors.push({
|
|
6200
|
+
rowNumber,
|
|
6201
|
+
code: "TRANSFORM_ERROR",
|
|
6202
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6203
|
+
});
|
|
6204
|
+
continue;
|
|
6205
|
+
}
|
|
6206
|
+
mapped = await this.engine.hooks.applyFilters("cms.transfer_before_import_row", mapped, {
|
|
6207
|
+
collectionSlug,
|
|
6208
|
+
rowNumber,
|
|
6209
|
+
raw
|
|
6210
|
+
});
|
|
6211
|
+
const identifierField = options.uniqueIdentifierField || activeMapping.options?.uniqueIdentifierField || this.detectUniqueField(config);
|
|
6212
|
+
let existingItem = null;
|
|
6213
|
+
if (identifierField && mapped.data[identifierField] !== void 0) {
|
|
6214
|
+
const val = mapped.data[identifierField];
|
|
6215
|
+
if (identifierField === "slug") existingItem = await this.engine.storage.getContentBySlug(collectionSlug, String(val));
|
|
6216
|
+
else existingItem = (await this.engine.storage.findContent(collectionSlug, {
|
|
6217
|
+
where: { [identifierField]: val },
|
|
6218
|
+
limit: 1
|
|
6219
|
+
})).items[0] || null;
|
|
6220
|
+
}
|
|
6221
|
+
if (existingItem) {
|
|
6222
|
+
if (onDuplicate === "skip") {
|
|
6223
|
+
skippedCount++;
|
|
6224
|
+
continue;
|
|
6225
|
+
}
|
|
6226
|
+
if (onDuplicate === "update") {
|
|
6227
|
+
try {
|
|
6228
|
+
const updated = await collService.update(existingItem.id, {
|
|
6229
|
+
title: mapped.title ?? existingItem.title,
|
|
6230
|
+
slug: mapped.slug ?? existingItem.slug,
|
|
6231
|
+
status: mapped.status ?? existingItem.status,
|
|
6232
|
+
data: {
|
|
6233
|
+
...existingItem.data,
|
|
6234
|
+
...mapped.data
|
|
6235
|
+
}
|
|
6236
|
+
}, options.authorId, options.revisionNote || "Import transfer update");
|
|
6237
|
+
if (updated) {
|
|
6238
|
+
updatedIds.push(updated.id);
|
|
6239
|
+
updatedCount++;
|
|
6240
|
+
await this.engine.hooks.doAction("cms.transfer_row_imported", {
|
|
6241
|
+
collectionSlug,
|
|
6242
|
+
action: "update",
|
|
6243
|
+
item: updated
|
|
6244
|
+
});
|
|
6245
|
+
}
|
|
6246
|
+
} catch (err) {
|
|
6247
|
+
errors.push({
|
|
6248
|
+
rowNumber,
|
|
6249
|
+
code: "VALIDATION_RULE_FAILED",
|
|
6250
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6251
|
+
});
|
|
6252
|
+
}
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
try {
|
|
6257
|
+
const created = await collService.create({
|
|
6258
|
+
title: mapped.title,
|
|
6259
|
+
slug: mapped.slug,
|
|
6260
|
+
status: mapped.status,
|
|
6261
|
+
data: mapped.data
|
|
6262
|
+
}, options.authorId);
|
|
6263
|
+
createdIds.push(created.id);
|
|
6264
|
+
importedCount++;
|
|
6265
|
+
await this.engine.hooks.doAction("cms.transfer_row_imported", {
|
|
6266
|
+
collectionSlug,
|
|
6267
|
+
action: "create",
|
|
6268
|
+
item: created
|
|
6269
|
+
});
|
|
6270
|
+
} catch (err) {
|
|
6271
|
+
errors.push({
|
|
6272
|
+
rowNumber,
|
|
6273
|
+
code: "VALIDATION_RULE_FAILED",
|
|
6274
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6275
|
+
});
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
const result = {
|
|
6279
|
+
success: errors.length === 0,
|
|
6280
|
+
collectionSlug,
|
|
6281
|
+
totalRows: rawRecords.length,
|
|
6282
|
+
importedCount,
|
|
6283
|
+
updatedCount,
|
|
6284
|
+
skippedCount,
|
|
6285
|
+
failedCount: rawRecords.length - (importedCount + updatedCount + skippedCount),
|
|
6286
|
+
createdIds,
|
|
6287
|
+
updatedIds,
|
|
6288
|
+
errors
|
|
6289
|
+
};
|
|
6290
|
+
await this.engine.hooks.doAction("cms.transfer_import_completed", result);
|
|
6291
|
+
await this.engine.hooks.doAction(`cms.${collectionSlug}_transferred`, result);
|
|
6292
|
+
return result;
|
|
6293
|
+
}
|
|
6294
|
+
/**
|
|
6295
|
+
* Export CMS collection records to JSON, Excel (.xlsx), or CSV.
|
|
6296
|
+
*/
|
|
6297
|
+
async exportData(collectionSlug, options = {}) {
|
|
6298
|
+
const config = this.getCollectionConfigOrThrow(collectionSlug);
|
|
6299
|
+
const collService = this.engine.collection(collectionSlug);
|
|
6300
|
+
const query = options.query || { limit: 1e4 };
|
|
6301
|
+
const items = (await collService.find(query)).items;
|
|
6302
|
+
const exportRecords = [];
|
|
6303
|
+
for (const item of items) {
|
|
6304
|
+
let mappedRow = mapCmsItemForExport(item, options.mapping, {
|
|
6305
|
+
includeId: options.includeId,
|
|
6306
|
+
includeTimestamps: options.includeTimestamps
|
|
6307
|
+
});
|
|
6308
|
+
mappedRow = await this.engine.hooks.applyFilters("cms.transfer_before_export_row", mappedRow, {
|
|
6309
|
+
collectionSlug,
|
|
6310
|
+
item
|
|
6311
|
+
});
|
|
6312
|
+
exportRecords.push(mappedRow);
|
|
6313
|
+
}
|
|
6314
|
+
return serializeSource(exportRecords, {
|
|
6315
|
+
format: options.format || "json",
|
|
6316
|
+
fileName: options.fileName || `${collectionSlug}_export_${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
|
|
6317
|
+
sheetName: options.sheetName || config.label
|
|
6318
|
+
});
|
|
6319
|
+
}
|
|
6320
|
+
getCollectionConfigOrThrow(collectionSlug) {
|
|
6321
|
+
const config = this.engine.getCollectionConfig(collectionSlug);
|
|
6322
|
+
if (!config) throw new Error(`[TransferService] Collection '${collectionSlug}' is not registered in the CMS engine.`);
|
|
6323
|
+
return config;
|
|
6324
|
+
}
|
|
6325
|
+
detectUniqueField(config) {
|
|
6326
|
+
const unique = config.fields.find((f) => Boolean(f.unique));
|
|
6327
|
+
if (unique) return unique.name;
|
|
6328
|
+
const slugField = config.fields.find((f) => f.type === "slug" || f.name === "slug");
|
|
6329
|
+
if (slugField) return slugField.name;
|
|
6330
|
+
}
|
|
6331
|
+
};
|
|
6332
|
+
//#endregion
|
|
6333
|
+
//#region src/plugins/transfer/routes.ts
|
|
6334
|
+
function json(data, status = 200, headers = {}) {
|
|
6335
|
+
return new Response(JSON.stringify(data), {
|
|
6336
|
+
status,
|
|
6337
|
+
headers: {
|
|
6338
|
+
"Content-Type": "application/json",
|
|
6339
|
+
"Access-Control-Allow-Origin": "*",
|
|
6340
|
+
...headers
|
|
6341
|
+
}
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
function badRequest(message) {
|
|
6345
|
+
return json({ error: message }, 400);
|
|
6346
|
+
}
|
|
6347
|
+
function notFound(message) {
|
|
6348
|
+
return json({ error: message }, 404);
|
|
6349
|
+
}
|
|
6350
|
+
function registerTransferRoutes(ctx, service, options = {}) {
|
|
6351
|
+
const prefix = (options.apiPrefix ?? "/api/transfer").replace(/\/+$/, "");
|
|
6352
|
+
ctx.registerRoute("GET", `${prefix}/collections`, async () => {
|
|
6353
|
+
try {
|
|
6354
|
+
return json({ collections: service.listCollections() });
|
|
6355
|
+
} catch (err) {
|
|
6356
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6357
|
+
}
|
|
6358
|
+
});
|
|
6359
|
+
ctx.registerRoute("GET", `${prefix}/presets`, async (_req, { url }) => {
|
|
6360
|
+
try {
|
|
6361
|
+
const collectionSlug = url.searchParams.get("collection") || void 0;
|
|
6362
|
+
return json({ presets: service.getPresets(collectionSlug) });
|
|
6363
|
+
} catch (err) {
|
|
6364
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6365
|
+
}
|
|
6366
|
+
});
|
|
6367
|
+
ctx.registerRoute("POST", `${prefix}/presets`, async (req) => {
|
|
6368
|
+
try {
|
|
6369
|
+
const body = await req.json();
|
|
6370
|
+
if (!body.collectionSlug || !Array.isArray(body.fields)) return badRequest("collectionSlug and fields array are required.");
|
|
6371
|
+
service.registerPreset(body);
|
|
6372
|
+
return json({
|
|
6373
|
+
success: true,
|
|
6374
|
+
preset: body
|
|
6375
|
+
}, 201);
|
|
6376
|
+
} catch (err) {
|
|
6377
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6378
|
+
}
|
|
6379
|
+
});
|
|
6380
|
+
ctx.registerRoute("GET", `${prefix}/:collection/schema`, async (_req, { params }) => {
|
|
6381
|
+
try {
|
|
6382
|
+
return json({
|
|
6383
|
+
schema: service.getCollectionSchema(params.collection),
|
|
6384
|
+
template: service.getMappingTemplate(params.collection)
|
|
6385
|
+
});
|
|
6386
|
+
} catch (err) {
|
|
6387
|
+
return notFound(err instanceof Error ? err.message : String(err));
|
|
6388
|
+
}
|
|
6389
|
+
});
|
|
6390
|
+
ctx.registerRoute("POST", `${prefix}/inspect`, async (req) => {
|
|
6391
|
+
try {
|
|
6392
|
+
const payload = await extractPayload(req);
|
|
6393
|
+
return json(await service.inspectSource(payload.data, {
|
|
6394
|
+
collectionSlug: payload.collectionSlug,
|
|
6395
|
+
format: payload.format,
|
|
6396
|
+
fileName: payload.fileName,
|
|
6397
|
+
sheetName: payload.sheetName
|
|
6398
|
+
}));
|
|
6399
|
+
} catch (err) {
|
|
6400
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6401
|
+
}
|
|
6402
|
+
});
|
|
6403
|
+
ctx.registerRoute("POST", `${prefix}/:collection/preview`, async (req, { params }) => {
|
|
6404
|
+
try {
|
|
6405
|
+
const payload = await extractPayload(req);
|
|
6406
|
+
return json(await service.validateImport(params.collection, payload.data, payload.mapping, {
|
|
6407
|
+
format: payload.format,
|
|
6408
|
+
fileName: payload.fileName,
|
|
6409
|
+
sheetName: payload.sheetName,
|
|
6410
|
+
onDuplicate: payload.onDuplicate
|
|
6411
|
+
}));
|
|
6412
|
+
} catch (err) {
|
|
6413
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6414
|
+
}
|
|
6415
|
+
});
|
|
6416
|
+
ctx.registerRoute("POST", `${prefix}/:collection/import`, async (req, { params }) => {
|
|
6417
|
+
try {
|
|
6418
|
+
const payload = await extractPayload(req);
|
|
6419
|
+
const result = await service.importData(params.collection, payload.data, payload.mapping, {
|
|
6420
|
+
format: payload.format,
|
|
6421
|
+
fileName: payload.fileName,
|
|
6422
|
+
sheetName: payload.sheetName,
|
|
6423
|
+
onDuplicate: payload.onDuplicate,
|
|
6424
|
+
uniqueIdentifierField: payload.uniqueIdentifierField,
|
|
6425
|
+
abortOnError: payload.abortOnError,
|
|
6426
|
+
authorId: payload.authorId,
|
|
6427
|
+
revisionNote: payload.revisionNote
|
|
6428
|
+
});
|
|
6429
|
+
return json(result, result.success ? 200 : 207);
|
|
6430
|
+
} catch (err) {
|
|
6431
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6432
|
+
}
|
|
6433
|
+
});
|
|
6434
|
+
ctx.registerRoute("POST", `${prefix}/:collection/export`, async (req, { params }) => {
|
|
6435
|
+
try {
|
|
6436
|
+
const body = await req.json();
|
|
6437
|
+
const result = await service.exportData(params.collection, {
|
|
6438
|
+
format: body.format,
|
|
6439
|
+
mapping: body.mapping,
|
|
6440
|
+
query: body.query,
|
|
6441
|
+
fileName: body.fileName,
|
|
6442
|
+
sheetName: body.sheetName,
|
|
6443
|
+
includeId: body.includeId,
|
|
6444
|
+
includeTimestamps: body.includeTimestamps
|
|
6445
|
+
});
|
|
6446
|
+
return new Response(result.data, {
|
|
6447
|
+
status: 200,
|
|
6448
|
+
headers: {
|
|
6449
|
+
"Content-Type": result.mimeType,
|
|
6450
|
+
"Content-Disposition": `attachment; filename="${result.fileName}"`,
|
|
6451
|
+
"Access-Control-Allow-Origin": "*"
|
|
6452
|
+
}
|
|
6453
|
+
});
|
|
6454
|
+
} catch (err) {
|
|
6455
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6456
|
+
}
|
|
6457
|
+
});
|
|
6458
|
+
ctx.registerRoute("GET", `${prefix}/:collection/export`, async (_req, { params, url }) => {
|
|
6459
|
+
try {
|
|
6460
|
+
const format = url.searchParams.get("format") || "json";
|
|
6461
|
+
const fileName = url.searchParams.get("fileName") || void 0;
|
|
6462
|
+
const status = url.searchParams.get("status") || void 0;
|
|
6463
|
+
const search = url.searchParams.get("search") || void 0;
|
|
6464
|
+
const result = await service.exportData(params.collection, {
|
|
6465
|
+
format,
|
|
6466
|
+
fileName,
|
|
6467
|
+
query: {
|
|
6468
|
+
status,
|
|
6469
|
+
search
|
|
6470
|
+
}
|
|
6471
|
+
});
|
|
6472
|
+
return new Response(result.data, {
|
|
6473
|
+
status: 200,
|
|
6474
|
+
headers: {
|
|
6475
|
+
"Content-Type": result.mimeType,
|
|
6476
|
+
"Content-Disposition": `attachment; filename="${result.fileName}"`,
|
|
6477
|
+
"Access-Control-Allow-Origin": "*"
|
|
6478
|
+
}
|
|
6479
|
+
});
|
|
6480
|
+
} catch (err) {
|
|
6481
|
+
return badRequest(err instanceof Error ? err.message : String(err));
|
|
6482
|
+
}
|
|
6483
|
+
});
|
|
6484
|
+
}
|
|
6485
|
+
/**
|
|
6486
|
+
* Helper to extract payload and options from JSON or Multipart requests.
|
|
6487
|
+
*/
|
|
6488
|
+
async function extractPayload(req) {
|
|
6489
|
+
if ((req.headers.get("content-type") || "").includes("multipart/form-data")) {
|
|
6490
|
+
const formData = await req.formData();
|
|
6491
|
+
const file = formData.get("file");
|
|
6492
|
+
let data = null;
|
|
6493
|
+
let fileName;
|
|
6494
|
+
if (file && typeof file === "object" && "arrayBuffer" in file) {
|
|
6495
|
+
data = new Uint8Array(await file.arrayBuffer());
|
|
6496
|
+
fileName = file.name;
|
|
6497
|
+
}
|
|
6498
|
+
const mappingRaw = formData.get("mapping");
|
|
6499
|
+
const mapping = mappingRaw ? JSON.parse(String(mappingRaw)) : void 0;
|
|
6500
|
+
return {
|
|
6501
|
+
data,
|
|
6502
|
+
collectionSlug: formData.get("collectionSlug") || void 0,
|
|
6503
|
+
format: formData.get("format") || void 0,
|
|
6504
|
+
fileName: formData.get("fileName") || fileName,
|
|
6505
|
+
sheetName: formData.get("sheetName") || void 0,
|
|
6506
|
+
mapping,
|
|
6507
|
+
onDuplicate: formData.get("onDuplicate") || void 0,
|
|
6508
|
+
uniqueIdentifierField: formData.get("uniqueIdentifierField") || void 0,
|
|
6509
|
+
abortOnError: formData.get("abortOnError") === "true",
|
|
6510
|
+
authorId: formData.get("authorId") || void 0,
|
|
6511
|
+
revisionNote: formData.get("revisionNote") || void 0
|
|
6512
|
+
};
|
|
6513
|
+
}
|
|
6514
|
+
const body = await req.json();
|
|
6515
|
+
let data = body.data;
|
|
6516
|
+
if (typeof body.base64 === "string") data = Buffer.from(body.base64, "base64");
|
|
6517
|
+
return {
|
|
6518
|
+
data: data !== void 0 ? data : body,
|
|
6519
|
+
collectionSlug: body.collectionSlug,
|
|
6520
|
+
format: body.format,
|
|
6521
|
+
fileName: body.fileName,
|
|
6522
|
+
sheetName: body.sheetName,
|
|
6523
|
+
mapping: body.mapping,
|
|
6524
|
+
onDuplicate: body.onDuplicate,
|
|
6525
|
+
uniqueIdentifierField: body.uniqueIdentifierField,
|
|
6526
|
+
abortOnError: body.abortOnError,
|
|
6527
|
+
authorId: body.authorId,
|
|
6528
|
+
revisionNote: body.revisionNote
|
|
6529
|
+
};
|
|
6530
|
+
}
|
|
6531
|
+
//#endregion
|
|
6532
|
+
//#region src/plugins/transfer/client.ts
|
|
6533
|
+
var TransferClient = class {
|
|
6534
|
+
client;
|
|
6535
|
+
options;
|
|
6536
|
+
service;
|
|
6537
|
+
prefix;
|
|
6538
|
+
constructor(client, options = {}) {
|
|
6539
|
+
this.client = client;
|
|
6540
|
+
this.options = options;
|
|
6541
|
+
this.prefix = (options.apiPrefix ?? "/api/transfer").replace(/\/+$/, "");
|
|
6542
|
+
const engine = client.getEngine();
|
|
6543
|
+
if (engine) this.service = new TransferService(engine, options);
|
|
6544
|
+
}
|
|
6545
|
+
/**
|
|
6546
|
+
* List all registered collections across all active plugins.
|
|
6547
|
+
*/
|
|
6548
|
+
async listCollections() {
|
|
6549
|
+
if (this.service) return this.service.listCollections();
|
|
6550
|
+
return (await this.client.request(`${this.prefix}/collections`)).collections;
|
|
6551
|
+
}
|
|
6552
|
+
/**
|
|
6553
|
+
* Get the collection schema and default mapping template.
|
|
6554
|
+
*/
|
|
6555
|
+
async getSchema(collectionSlug) {
|
|
6556
|
+
if (this.service) return {
|
|
6557
|
+
schema: this.service.getCollectionSchema(collectionSlug),
|
|
6558
|
+
template: this.service.getMappingTemplate(collectionSlug)
|
|
6559
|
+
};
|
|
6560
|
+
return this.client.request(`${this.prefix}/${collectionSlug}/schema`);
|
|
6561
|
+
}
|
|
6562
|
+
/**
|
|
6563
|
+
* Inspect a source file or payload.
|
|
6564
|
+
*/
|
|
6565
|
+
async inspect(data, options = {}) {
|
|
6566
|
+
if (this.service) return this.service.inspectSource(data, options);
|
|
6567
|
+
return this.client.request(`${this.prefix}/inspect`, {
|
|
6568
|
+
method: "POST",
|
|
6569
|
+
body: JSON.stringify({
|
|
6570
|
+
data,
|
|
6571
|
+
...options
|
|
6572
|
+
})
|
|
6573
|
+
});
|
|
6574
|
+
}
|
|
6575
|
+
/**
|
|
6576
|
+
* Dry-run validation of a source batch against a target collection and database constraints.
|
|
6577
|
+
*/
|
|
6578
|
+
async preview(collectionSlug, data, mapping, options = {}) {
|
|
6579
|
+
if (this.service) return this.service.validateImport(collectionSlug, data, mapping, options);
|
|
6580
|
+
return this.client.request(`${this.prefix}/${collectionSlug}/preview`, {
|
|
6581
|
+
method: "POST",
|
|
6582
|
+
body: JSON.stringify({
|
|
6583
|
+
data,
|
|
6584
|
+
mapping,
|
|
6585
|
+
...options
|
|
6586
|
+
})
|
|
6587
|
+
});
|
|
6588
|
+
}
|
|
6589
|
+
/**
|
|
6590
|
+
* Execute an import batch into a target collection.
|
|
6591
|
+
*/
|
|
6592
|
+
async import(collectionSlug, data, mapping, options = {}) {
|
|
6593
|
+
if (this.service) return this.service.importData(collectionSlug, data, mapping, options);
|
|
6594
|
+
return this.client.request(`${this.prefix}/${collectionSlug}/import`, {
|
|
6595
|
+
method: "POST",
|
|
6596
|
+
body: JSON.stringify({
|
|
6597
|
+
data,
|
|
6598
|
+
mapping,
|
|
6599
|
+
...options
|
|
6600
|
+
})
|
|
6601
|
+
});
|
|
6602
|
+
}
|
|
6603
|
+
/**
|
|
6604
|
+
* Export collection records to JSON, Excel, or CSV.
|
|
6605
|
+
*/
|
|
6606
|
+
async export(collectionSlug, options = {}) {
|
|
6607
|
+
if (this.service) return this.service.exportData(collectionSlug, options);
|
|
6608
|
+
return await this.client.request(`${this.prefix}/${collectionSlug}/export`, {
|
|
6609
|
+
method: "POST",
|
|
6610
|
+
body: JSON.stringify(options)
|
|
6611
|
+
});
|
|
6612
|
+
}
|
|
6613
|
+
};
|
|
6614
|
+
/**
|
|
6615
|
+
* Access or instantiate the TransferClient associated with a CMSClient instance.
|
|
6616
|
+
*/
|
|
6617
|
+
function getTransferClient(client, options) {
|
|
6618
|
+
if (client.__transferClient) return client.__transferClient;
|
|
6619
|
+
const tc = new TransferClient(client, options);
|
|
6620
|
+
client.__transferClient = tc;
|
|
6621
|
+
return tc;
|
|
6622
|
+
}
|
|
6623
|
+
//#endregion
|
|
6624
|
+
//#region src/plugins/transfer/index.ts
|
|
6625
|
+
/**
|
|
6626
|
+
* @azlib/cms - Built-in Universal Transfer Plugin
|
|
6627
|
+
*/
|
|
6628
|
+
/**
|
|
6629
|
+
* Built-in Universal Data Transfer plugin factory for @azlib/cms.
|
|
6630
|
+
* Equips the CMS engine with dynamic, schema-driven import and export capabilities
|
|
6631
|
+
* across all collections, featuring file schema discovery, definition mapping,
|
|
6632
|
+
* multi-level data validation, database constraint verification, and multi-format support.
|
|
6633
|
+
*/
|
|
6634
|
+
const transferPlugin = definePlugin((options) => {
|
|
6635
|
+
const opts = options || {};
|
|
6636
|
+
return {
|
|
6637
|
+
name: "transfer",
|
|
6638
|
+
version: "1.0.0",
|
|
6639
|
+
description: "Universal schema-driven data transfer plugin supporting import/export, definition mapping, and constraint validation",
|
|
6640
|
+
setup(ctx) {
|
|
6641
|
+
const service = new TransferService(ctx.engine, opts);
|
|
6642
|
+
ctx.engine.__transferService = service;
|
|
6643
|
+
if (opts.enableRoutes !== false) registerTransferRoutes(ctx, service, opts);
|
|
6644
|
+
}
|
|
6645
|
+
};
|
|
6646
|
+
});
|
|
6647
|
+
/**
|
|
6648
|
+
* Retrieve the active TransferService instance associated with a CMSEngine.
|
|
6649
|
+
*/
|
|
6650
|
+
function getTransferService(engine, options) {
|
|
6651
|
+
if (engine.__transferService) return engine.__transferService;
|
|
6652
|
+
const service = new TransferService(engine, options);
|
|
6653
|
+
engine.__transferService = service;
|
|
6654
|
+
return service;
|
|
6655
|
+
}
|
|
6656
|
+
//#endregion
|
|
4829
6657
|
exports.CMSClient = CMSClient;
|
|
4830
6658
|
exports.CMSEngine = CMSEngine;
|
|
4831
6659
|
exports.CMSRouter = CMSRouter;
|
|
@@ -4836,6 +6664,8 @@ exports.EcommerceClient = EcommerceClient;
|
|
|
4836
6664
|
exports.EcommerceService = EcommerceService;
|
|
4837
6665
|
exports.HRMSClient = HRMSClient;
|
|
4838
6666
|
exports.HRMSService = HRMSService;
|
|
6667
|
+
exports.HRMS_EMPLOYEE_TRANSFER_PRESET = HRMS_EMPLOYEE_TRANSFER_PRESET;
|
|
6668
|
+
exports.HRMS_EMPLOYER_TRANSFER_PRESET = HRMS_EMPLOYER_TRANSFER_PRESET;
|
|
4839
6669
|
exports.HooksManager = HooksManager;
|
|
4840
6670
|
exports.MediaManager = MediaManager;
|
|
4841
6671
|
exports.MemoryStorageAdapter = MemoryStorageAdapter;
|
|
@@ -4843,7 +6673,10 @@ exports.OptionsManager = OptionsManager;
|
|
|
4843
6673
|
exports.RBACManager = RBACManager;
|
|
4844
6674
|
exports.RevisionManager = RevisionManager;
|
|
4845
6675
|
exports.TaxonomyManager = TaxonomyManager;
|
|
6676
|
+
exports.TransferClient = TransferClient;
|
|
6677
|
+
exports.TransferService = TransferService;
|
|
4846
6678
|
exports.VALID_STATUS_TRANSITIONS = VALID_STATUS_TRANSITIONS;
|
|
6679
|
+
exports.applyFieldTransform = applyFieldTransform;
|
|
4847
6680
|
exports.collection = collection;
|
|
4848
6681
|
exports.createAttendanceCollection = createAttendanceCollection;
|
|
4849
6682
|
exports.createCMSEngine = createCMSEngine;
|
|
@@ -4861,14 +6694,32 @@ exports.createProductCollection = createProductCollection;
|
|
|
4861
6694
|
exports.defaultHooks = defaultHooks;
|
|
4862
6695
|
exports.defineConfig = defineConfig;
|
|
4863
6696
|
exports.definePlugin = definePlugin;
|
|
6697
|
+
exports.detectFormat = detectFormat;
|
|
4864
6698
|
exports.ecommercePlugin = ecommercePlugin;
|
|
4865
6699
|
exports.fields = fields;
|
|
6700
|
+
exports.generateSuggestedMapping = generateSuggestedMapping;
|
|
4866
6701
|
exports.getEcommerceClient = getEcommerceClient;
|
|
4867
6702
|
exports.getEcommerceService = getEcommerceService;
|
|
4868
6703
|
exports.getHRMSClient = getHRMSClient;
|
|
4869
6704
|
exports.getHRMSService = getHRMSService;
|
|
6705
|
+
exports.getTransferClient = getTransferClient;
|
|
6706
|
+
exports.getTransferService = getTransferService;
|
|
4870
6707
|
exports.hrmsPlugin = hrmsPlugin;
|
|
6708
|
+
exports.inspectSource = inspectSource;
|
|
6709
|
+
exports.mapCmsItemForExport = mapCmsItemForExport;
|
|
6710
|
+
exports.mapSourceRecord = mapSourceRecord;
|
|
4871
6711
|
exports.normalizeConfig = normalizeConfig;
|
|
6712
|
+
exports.parseCsvSource = parseCsvSource;
|
|
6713
|
+
exports.parseExcelSource = parseExcelSource;
|
|
6714
|
+
exports.parseJsonSource = parseJsonSource;
|
|
6715
|
+
exports.parseSource = parseSource;
|
|
4872
6716
|
exports.resolveUniqueSlug = resolveUniqueSlug;
|
|
6717
|
+
exports.serializeCsv = serializeCsv;
|
|
6718
|
+
exports.serializeExcel = serializeExcel;
|
|
6719
|
+
exports.serializeJson = serializeJson;
|
|
6720
|
+
exports.serializeSource = serializeSource;
|
|
4873
6721
|
exports.slugify = slugify;
|
|
6722
|
+
exports.summarizeCollection = summarizeCollection;
|
|
6723
|
+
exports.transferPlugin = transferPlugin;
|
|
4874
6724
|
exports.validateAndNormalizeData = validateAndNormalizeData;
|
|
6725
|
+
exports.validateTransferBatch = validateTransferBatch;
|