test again

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-13 12:46:38 +08:00
parent e5e2580c7d
commit f5254f7571
12 changed files with 441 additions and 461 deletions
+30 -2
View File
@@ -7,6 +7,7 @@ const Asset = require("../../models/assets/assets.mdl");
const chibi = require("../../services/chibisafe.service"); const chibi = require("../../services/chibisafe.service");
const s3 = require("../../services/s3.service"); const s3 = require("../../services/s3.service");
const mediaToken = require("../../services/mediaToken.service"); const mediaToken = require("../../services/mediaToken.service");
const uploadProgress = require("../../services/uploadProgress.service");
const { extractVideoMeta } = require("../../services/ffprobe.service"); const { extractVideoMeta } = require("../../services/ffprobe.service");
const R = require('../../utils/response.util'); const R = require('../../utils/response.util');
const { paginate } = require("../../utils/paginate.util"); const { paginate } = require("../../utils/paginate.util");
@@ -328,7 +329,7 @@ exports.getAsset = async (req, res) => {
// per-file thumbnail step, so videos land with thumbnail_url null and pick // per-file thumbnail step, so videos land with thumbnail_url null and pick
// one up later via the existing "thumbnail-only" path in updateAsset(). // one up later via the existing "thumbnail-only" path in updateAsset().
// //
async function createAssetFromUpload({ file, thumbFile, body, user, requireVideoThumbnail = true }) { async function createAssetFromUpload({ file, thumbFile, body, user, requireVideoThumbnail = true, uploadId }) {
const uploadedFiles = []; // [{ key, provider }] const uploadedFiles = []; // [{ key, provider }]
try { try {
@@ -367,11 +368,24 @@ async function createAssetFromUpload({ file, thumbFile, body, user, requireVideo
if (usesProvider) { if (usesProvider) {
const svc = getProvider(storage_provider); const svc = getProvider(storage_provider);
// Real Express -> Garage progress, main file only (not the thumbnail —
// it's small enough that tracking it wouldn't add anything useful).
// Relayed live to the browser over SSE; see uploadProgress.service.js.
const onProgress = (storage_provider === "s3" && uploadId)
? ({ loaded, total }) => uploadProgress.publish(uploadId, {
phase: "storing",
loaded,
total,
pct: total ? Math.round((loaded / total) * 100) : 0,
})
: undefined;
const result = await svc.uploadFile({ const result = await svc.uploadFile({
buffer: file.buffer, buffer: file.buffer,
originalname: file.originalname, originalname: file.originalname,
mimetype: mime_type, mimetype: mime_type,
ownerType: file_type, ownerType: file_type,
onProgress,
}); });
file_url = result.url; file_url = result.url;
storage_key_resolved = result.uuid; storage_key_resolved = result.uuid;
@@ -497,21 +511,35 @@ async function createAssetFromUpload({ file, thumbFile, body, user, requireVideo
} }
exports.uploadAsset = async (req, res) => { exports.uploadAsset = async (req, res) => {
const { uploadId } = req.body;
try { try {
const file = req.files?.file?.[0]; const file = req.files?.file?.[0];
const thumbFile = req.files?.thumbnail?.[0]; const thumbFile = req.files?.thumbnail?.[0];
const asset = await createAssetFromUpload({ file, thumbFile, body: req.body, user: req.user }); const asset = await createAssetFromUpload({ file, thumbFile, body: req.body, user: req.user, uploadId });
invalidateListCache(); invalidateListCache();
if (uploadId) uploadProgress.complete(uploadId, { phase: "done", pct: 100 });
return R.success(res, "Asset uploaded.", { data: asset }, 201); return R.success(res, "Asset uploaded.", { data: asset }, 201);
} catch (err) { } catch (err) {
console.error("[ASSET][UPLOAD]", err); console.error("[ASSET][UPLOAD]", err);
if (uploadId) uploadProgress.complete(uploadId, { phase: "error" });
if (err.status) return R.error(res, err.message, err.status, { detail: err.chibiBody }); if (err.status) return R.error(res, err.message, err.status, { detail: err.chibiBody });
return R.error(res, "Internal server error.", 500); return R.error(res, "Internal server error.", 500);
} }
}; };
// ─── UPLOAD PROGRESS (SSE) ──────────────────────────────────────────────────
//
// Client opens this before POSTing the file, correlated by a client-generated
// uploadId sent as a form field on the upload request itself. Streams the
// real Express -> Garage httpUploadProgress events from s3.service.js's
// Upload — not a simulated or estimated number.
//
exports.streamUploadProgress = (req, res) => {
uploadProgress.subscribe(req.params.uploadId, res);
};
// ─── UPLOAD (batch) ───────────────────────────────────────────────────────── // ─── UPLOAD (batch) ─────────────────────────────────────────────────────────
// //
// Accepts multiple files under the "files" field in one multipart request, // Accepts multiple files under the "files" field in one multipart request,
@@ -17,14 +17,12 @@ exports.getUsersDashboard = async (req, res) => {
const [ const [
totalUsers, totalUsers,
activeUsers, activeUsers,
verifiedUsers,
archivedUsers, archivedUsers,
accTypeBreakdown, accTypeBreakdown,
regTypeBreakdown, regTypeBreakdown,
] = await Promise.all([ ] = await Promise.all([
mdl_Users.count({ paranoid: false }), mdl_Users.count({ paranoid: false }),
mdl_Users.count({ where: { is_active: true } }), mdl_Users.count({ where: { is_active: true } }),
mdl_Users.count({ where: { is_verified: true } }),
mdl_Users.count({ where: { deletedAt: { [Op.ne]: null } }, paranoid: false }), mdl_Users.count({ where: { deletedAt: { [Op.ne]: null } }, paranoid: false }),
mdl_Users.findAll({ mdl_Users.findAll({
attributes: ['acc_type', [fn('COUNT', col('user_id')), 'count']], attributes: ['acc_type', [fn('COUNT', col('user_id')), 'count']],
@@ -46,8 +44,6 @@ exports.getUsersDashboard = async (req, res) => {
{ key: 'total', label: 'Total Users', value: totalUsers }, { key: 'total', label: 'Total Users', value: totalUsers },
{ key: 'active', label: 'Active Users', value: activeUsers }, { key: 'active', label: 'Active Users', value: activeUsers },
{ key: 'inactive', label: 'Inactive Users', value: totalUsers - archivedUsers - activeUsers }, { key: 'inactive', label: 'Inactive Users', value: totalUsers - archivedUsers - activeUsers },
{ key: 'verified', label: 'Verified', value: verifiedUsers },
{ key: 'archived', label: 'Archived', value: archivedUsers },
], ],
breakdowns: [ breakdowns: [
{ {
@@ -30,7 +30,15 @@ function buildWhere(query, extraWhere = {}) {
if (query.from || query.to) { if (query.from || query.to) {
where.created_at = {}; where.created_at = {};
if (query.from) where.created_at[Op.gte] = new Date(query.from); if (query.from) where.created_at[Op.gte] = new Date(query.from);
if (query.to) where.created_at[Op.lte] = new Date(query.to); if (query.to) {
// `to` arrives as a date-only string (e.g. "2026-06-04"), which parses
// to that day's UTC midnight — an Op.lte against midnight excludes
// every event that happened later the same day. Push it to the last
// instant of that calendar day instead.
const to = new Date(query.to);
to.setUTCHours(23, 59, 59, 999);
where.created_at[Op.lte] = to;
}
} }
return where; return where;
+60 -2
View File
@@ -34,10 +34,48 @@ const { excludeAttributes: usersExclude, jsonbSchemas: usersSchemas, computedAtt
const EXCLUDED = ['password', 'otp_code', 'otp_expires_at', 'must_change_password', 'password_expires_at']; const EXCLUDED = ['password', 'otp_code', 'otp_expires_at', 'must_change_password', 'password_expires_at'];
const auditByFields = ['createdBy', 'updatedBy', 'deletedBy']; const auditByFields = ['createdBy', 'updatedBy', 'deletedBy'];
// ─── GROUPS FILTER ────────────────────────────────────────────────────────────
//
// "groups" is a M2M association, not a real column on `users` — buildQuery's
// generic Sequelize.col()-based filtering can't touch it. Pull any `groups`
// filter out of the request's filter list (so paginate() never sees a field
// it can't resolve) and turn it into an EXISTS subquery instead. EXISTS keeps
// the LEFT JOIN group data on each row intact (all of a user's groups still
// show up), while only matching users who belong to at least one of the
// selected groups.
function extractGroupsFilter(req) {
let filters = [];
try { filters = JSON.parse(req.query.filters || '[]'); } catch { filters = []; }
const groupsFilter = filters.find((f) => f.id === 'groups');
const remaining = filters.filter((f) => f.id !== 'groups');
req.query.filters = JSON.stringify(remaining);
if (!groupsFilter?.value) return null;
const groupIds = (Array.isArray(groupsFilter.value) ? groupsFilter.value : [groupsFilter.value])
.map((v) => parseInt(v, 10))
.filter((v) => !Number.isNaN(v));
return groupIds.length ? groupIds : null;
}
function groupsExistsWhere(groupIds) {
if (!groupIds) return undefined;
return Sequelize.literal(`EXISTS (
SELECT 1 FROM "user_group_members" ugm
WHERE ugm.user_id = "User"."user_id"
AND ugm."deletedAt" IS NULL
AND ugm.group_id IN (${groupIds.join(',')})
)`);
}
// ─── GET ALL ────────────────────────────────────────────────────────────────── // ─── GET ALL ──────────────────────────────────────────────────────────────────
exports.getUsers = async (req, res) => { exports.getUsers = async (req, res) => {
try { try {
const groupIds = extractGroupsFilter(req);
const result = await paginate(mdl_Users, req, { const result = await paginate(mdl_Users, req, {
excludeAttributes: usersExclude, excludeAttributes: usersExclude,
jsonbSchemas: usersSchemas, jsonbSchemas: usersSchemas,
@@ -46,6 +84,7 @@ exports.getUsers = async (req, res) => {
context: "list", context: "list",
auditOptions: { mdl_Users, parentAlias: 'User' }, auditOptions: { mdl_Users, parentAlias: 'User' },
findOptions: { findOptions: {
where: groupsExistsWhere(groupIds),
include: [{ include: [{
model: mdl_UserGroups, model: mdl_UserGroups,
as: 'groups', as: 'groups',
@@ -470,18 +509,37 @@ exports.terminateSession = async (req, res) => {
// ─── FIELD VALUES ───────────────────────────────────────────────────────────── // ─── FIELD VALUES ─────────────────────────────────────────────────────────────
exports.getUserFieldValues = getFieldValues(mdl_Users, "USER", { const getUserFieldValuesBase = getFieldValues(mdl_Users, "USER", {
blockedFields: ["password", "otp_code", "otp_expires_at", "personal_info"], blockedFields: ["password", "otp_code", "otp_expires_at", "personal_info"],
extraDateFields: ["modifiedAt", "ban_expires_at"], extraDateFields: ["modifiedAt", "ban_expires_at"],
allowJsonb: true, allowJsonb: true,
selfJoin: true, selfJoin: true,
}); });
// "groups" isn't a Users model attribute, so the generic getFieldValues()
// helper can't resolve it — special-case it here, delegate everything else.
exports.getUserFieldValues = async (req, res) => {
if (req.query.field === 'groups') {
const groups = await mdl_UserGroups.findAll({
attributes: [['group_id', 'value'], ['name', 'label']],
order: [['name', 'ASC']],
raw: true,
});
return R.success(res, 'Field values retrieved.', groups);
}
return getUserFieldValuesBase(req, res);
};
// ─── ARCHIVED ───────────────────────────────────────────────────────────────── // ─── ARCHIVED ─────────────────────────────────────────────────────────────────
// ─── GET ARCHIVED USERS ─────────────────────────────────────────────────────── // ─── GET ARCHIVED USERS ───────────────────────────────────────────────────────
exports.getArchivedUsers = async (req, res) => { exports.getArchivedUsers = async (req, res) => {
try { try {
const groupIds = extractGroupsFilter(req);
const archivedWhere = { deletedAt: { [Op.ne]: null }, is_active: false };
const groupsWhere = groupsExistsWhere(groupIds);
const result = await paginate(mdl_Users, req, { const result = await paginate(mdl_Users, req, {
excludeAttributes: usersExclude, excludeAttributes: usersExclude,
jsonbSchemas: usersSchemas, jsonbSchemas: usersSchemas,
@@ -491,7 +549,7 @@ exports.getArchivedUsers = async (req, res) => {
auditOptions: { mdl_Users, parentAlias: 'User' }, auditOptions: { mdl_Users, parentAlias: 'User' },
findOptions: { findOptions: {
paranoid: false, paranoid: false,
where: { deletedAt: { [Op.ne]: null }, is_active: false }, where: groupsWhere ? { [Op.and]: [archivedWhere, groupsWhere] } : archivedWhere,
include: [{ include: [{
model: mdl_UserGroups, model: mdl_UserGroups,
as: 'groups', as: 'groups',
+1
View File
@@ -52,6 +52,7 @@ const computedAttributes = [
label: "Groups", label: "Groups",
type: "array", // tells the paginator this is a pre-joined association array type: "array", // tells the paginator this is a pre-joined association array
order: 5, order: 5,
filterable: true, // handled specially in the controller — see extractGroupsFilter()
}, },
]; ];
+237 -434
View File
@@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1068.0", "@aws-sdk/client-s3": "^3.1068.0",
"@aws-sdk/lib-storage": "^3.1085.0",
"@aws-sdk/s3-request-presigner": "^3.1068.0", "@aws-sdk/s3-request-presigner": "^3.1068.0",
"axios": "^1.16.0", "axios": "^1.16.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
@@ -46,107 +47,16 @@
"sequelize-cli": "^6.6.5" "sequelize-cli": "^6.6.5"
} }
}, },
"node_modules/@aws-crypto/crc32": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
"integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-crypto/crc32c": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz",
"integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-crypto/sha1-browser": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz",
"integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/supports-web-crypto": "^5.2.0",
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"@aws-sdk/util-locate-window": "^3.0.0",
"@smithy/util-utf8": "^2.0.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-crypto/sha256-browser": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz",
"integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"@aws-crypto/supports-web-crypto": "^5.2.0",
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"@aws-sdk/util-locate-window": "^3.0.0",
"@smithy/util-utf8": "^2.0.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-crypto/sha256-js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
"integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-crypto/supports-web-crypto": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz",
"integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
}
},
"node_modules/@aws-crypto/util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
"integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.222.0",
"@smithy/util-utf8": "^2.0.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-sdk/checksums": { "node_modules/@aws-sdk/checksums": {
"version": "3.1000.5", "version": "3.1000.16",
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.5.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.16.tgz",
"integrity": "sha512-zOXUUnilC6lgCsQtp77p/QNPmRlTES9Xi6tlDwbR6kfC/kz5PCzZckgHWm5z+8DskdwuMAbFDq61x3zr10GEEQ==", "integrity": "sha512-EKnvkXSmz3IpA99tCNuI+dLFXyZyClSm8zns9sB/elvkU+MTuomAs6toJMPMBf98/fICG/urXDkzGz0/c3yyAQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/crc32": "5.2.0", "@aws-sdk/core": "^3.975.1",
"@aws-crypto/crc32c": "5.2.0", "@aws-sdk/types": "^3.974.0",
"@aws-crypto/util": "5.2.0", "@smithy/core": "^3.29.2",
"@aws-sdk/core": "^3.974.20", "@smithy/types": "^4.16.0",
"@aws-sdk/types": "^3.973.12",
"@smithy/core": "^3.24.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -154,24 +64,21 @@
} }
}, },
"node_modules/@aws-sdk/client-s3": { "node_modules/@aws-sdk/client-s3": {
"version": "3.1068.0", "version": "3.1085.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1068.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1085.0.tgz",
"integrity": "sha512-lFgaIpxZvloNbJvQ337YPdMXhzI2zJdDw13nATVGnkAGNoNPx4ksD84AQAcuW75hsaaMaIuNmXU9sSx6+FTirA==", "integrity": "sha512-O0xe8sR50AYkwxlvRRsV0qytEO2dtXQTQ1CF3YBBdE5xtVkbu27H0vGa1mjQi1/+fbYM80AWEIPai5jZmXyubw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha1-browser": "5.2.0", "@aws-sdk/checksums": "^3.1000.16",
"@aws-crypto/sha256-browser": "5.2.0", "@aws-sdk/core": "^3.975.1",
"@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/credential-provider-node": "^3.972.66",
"@aws-sdk/core": "^3.974.20", "@aws-sdk/middleware-sdk-s3": "^3.972.62",
"@aws-sdk/credential-provider-node": "^3.972.55", "@aws-sdk/signature-v4-multi-region": "^3.996.39",
"@aws-sdk/middleware-flexible-checksums": "^3.974.30", "@aws-sdk/types": "^3.974.0",
"@aws-sdk/middleware-sdk-s3": "^3.972.51", "@smithy/core": "^3.29.2",
"@aws-sdk/signature-v4-multi-region": "^3.996.34", "@smithy/fetch-http-handler": "^5.6.4",
"@aws-sdk/types": "^3.973.12", "@smithy/node-http-handler": "^4.9.4",
"@smithy/core": "^3.24.6", "@smithy/types": "^4.16.0",
"@smithy/fetch-http-handler": "^5.4.6",
"@smithy/node-http-handler": "^4.7.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -179,17 +86,17 @@
} }
}, },
"node_modules/@aws-sdk/core": { "node_modules/@aws-sdk/core": {
"version": "3.974.20", "version": "3.975.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.20.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz",
"integrity": "sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==", "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@aws-sdk/xml-builder": "^3.972.29", "@aws-sdk/xml-builder": "^3.972.34",
"@aws/lambda-invoke-store": "^0.2.2", "@aws/lambda-invoke-store": "^0.3.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/signature-v4": "^5.4.6", "@smithy/signature-v4": "^5.6.3",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
@@ -198,15 +105,15 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-env": { "node_modules/@aws-sdk/credential-provider-env": {
"version": "3.972.46", "version": "3.972.57",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.46.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz",
"integrity": "sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==", "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -214,17 +121,17 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-http": { "node_modules/@aws-sdk/credential-provider-http": {
"version": "3.972.48", "version": "3.972.59",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.48.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz",
"integrity": "sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==", "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/fetch-http-handler": "^5.4.6", "@smithy/fetch-http-handler": "^5.6.4",
"@smithy/node-http-handler": "^4.7.6", "@smithy/node-http-handler": "^4.9.4",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -232,23 +139,23 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-ini": { "node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.972.53", "version": "3.973.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.53.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz",
"integrity": "sha512-ZfdhIOR41q8TcWEnUac+gCOb+O2LBWdHLmjedXpXz4IEFW2ppNuFcm6p0sMTavpM+zD5TYfpH5Gp7guRyqSgsQ==", "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-env": "^3.972.57",
"@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-http": "^3.972.59",
"@aws-sdk/credential-provider-login": "^3.972.52", "@aws-sdk/credential-provider-login": "^3.972.63",
"@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.57",
"@aws-sdk/credential-provider-sso": "^3.972.52", "@aws-sdk/credential-provider-sso": "^3.973.1",
"@aws-sdk/credential-provider-web-identity": "^3.972.52", "@aws-sdk/credential-provider-web-identity": "^3.972.63",
"@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/nested-clients": "^3.997.31",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/credential-provider-imds": "^4.3.7", "@smithy/credential-provider-imds": "^4.4.7",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -256,16 +163,16 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-login": { "node_modules/@aws-sdk/credential-provider-login": {
"version": "3.972.52", "version": "3.972.63",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.52.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz",
"integrity": "sha512-9hu2oR0qH7Fst5Tzdx+UWxm+w5zCXtErTLtOOW5hwwQc170CLwOeniRxyFY6s9mHfGEfC5zFukNBdKBwJR8mhQ==", "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/nested-clients": "^3.997.31",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -273,21 +180,21 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-node": { "node_modules/@aws-sdk/credential-provider-node": {
"version": "3.972.55", "version": "3.972.66",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.55.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.66.tgz",
"integrity": "sha512-zMGLa/dhESVqmCD7mmIFFKSwSFrJGScvCXcjvBZEVOOMauFS5JRQvLTMukFpMEFWiV6dTAlsen2ATDBulLPtbg==", "integrity": "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/credential-provider-env": "^3.972.46", "@aws-sdk/credential-provider-env": "^3.972.57",
"@aws-sdk/credential-provider-http": "^3.972.48", "@aws-sdk/credential-provider-http": "^3.972.59",
"@aws-sdk/credential-provider-ini": "^3.972.53", "@aws-sdk/credential-provider-ini": "^3.973.1",
"@aws-sdk/credential-provider-process": "^3.972.46", "@aws-sdk/credential-provider-process": "^3.972.57",
"@aws-sdk/credential-provider-sso": "^3.972.52", "@aws-sdk/credential-provider-sso": "^3.973.1",
"@aws-sdk/credential-provider-web-identity": "^3.972.52", "@aws-sdk/credential-provider-web-identity": "^3.972.63",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/credential-provider-imds": "^4.3.7", "@smithy/credential-provider-imds": "^4.4.7",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -295,15 +202,15 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-process": { "node_modules/@aws-sdk/credential-provider-process": {
"version": "3.972.46", "version": "3.972.57",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.46.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz",
"integrity": "sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==", "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -311,17 +218,17 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-sso": { "node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.972.52", "version": "3.973.1",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.52.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz",
"integrity": "sha512-nb2/n4o/HQf+FVpVbZe9vCTFngmuDoIsltMgLAtjixaKzvzhB4J8WSDFyWgnErgLHk55ctWH+I4PU+LIHhyffg==", "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/nested-clients": "^3.997.31",
"@aws-sdk/token-providers": "3.1066.0", "@aws-sdk/token-providers": "3.1083.0",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -329,46 +236,53 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-web-identity": { "node_modules/@aws-sdk/credential-provider-web-identity": {
"version": "3.972.52", "version": "3.972.63",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.52.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz",
"integrity": "sha512-lKj6aRSGbqLmpYmM24bY7a1Xmfcq2vkE3hv8CSPYfc1yCu0BPu/XEJ1L4Fm61MsU6ULLNSG8UGsffNoFUBjESA==", "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/nested-clients": "^3.997.31",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
} }
}, },
"node_modules/@aws-sdk/middleware-flexible-checksums": { "node_modules/@aws-sdk/lib-storage": {
"version": "3.974.30", "version": "3.1085.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.30.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.1085.0.tgz",
"integrity": "sha512-OaIhub+3yTgfFWPzKO8OzOZFIMUoJaiS5v67y3spQg7SoULGoMx4jKVBbE+uhnzkiZXQ+rEDS0RqrK4/aD1yJw==", "integrity": "sha512-S+yCGIMxQ5Zs2A5ZDdYfXwOxu5kKjBaCQVOxp6+mnwCQYXUNkBD/aKCnW1eniMTavzz3YidhD5eTFpDlcUv2gQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/checksums": "^3.1000.5", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.16.0",
"buffer": "5.6.0",
"events": "3.3.0",
"stream-browserify": "3.0.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
},
"peerDependencies": {
"@aws-sdk/client-s3": "^3.1085.0"
} }
}, },
"node_modules/@aws-sdk/middleware-sdk-s3": { "node_modules/@aws-sdk/middleware-sdk-s3": {
"version": "3.972.51", "version": "3.972.62",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.51.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.62.tgz",
"integrity": "sha512-keQgcIUTcHL0Qn7guhsuLaxQU36r9norCrxgaPH4DNCwon4TPtXdI/UdYuycl9vj3Dlwc3YR1dfL3U+6iIwJ6w==", "integrity": "sha512-k8JJwYXVYlOOjWnPZDThQS1xDFJgi5Dokt73qFlDtrZAbdcint5aIdjB9XgJAAQVP5OoqcefQmh1FYXiPpvsvw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/signature-v4-multi-region": "^3.996.34", "@aws-sdk/signature-v4-multi-region": "^3.996.39",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -376,20 +290,18 @@
} }
}, },
"node_modules/@aws-sdk/nested-clients": { "node_modules/@aws-sdk/nested-clients": {
"version": "3.997.20", "version": "3.997.31",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.20.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz",
"integrity": "sha512-IYJuLpXp2DEILVQpQOy0PMpkftv0AHEOCn52o0atyOaumA0CdWQ3klPyXdViGYLbNpESsVFMVybvHUeZAuiGxA==", "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-sdk/core": "^3.975.1",
"@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/signature-v4-multi-region": "^3.996.39",
"@aws-sdk/core": "^3.974.20", "@aws-sdk/types": "^3.974.0",
"@aws-sdk/signature-v4-multi-region": "^3.996.34", "@smithy/core": "^3.29.2",
"@aws-sdk/types": "^3.973.12", "@smithy/fetch-http-handler": "^5.6.4",
"@smithy/core": "^3.24.6", "@smithy/node-http-handler": "^4.9.4",
"@smithy/fetch-http-handler": "^5.4.6", "@smithy/types": "^4.16.0",
"@smithy/node-http-handler": "^4.7.6",
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -414,14 +326,14 @@
} }
}, },
"node_modules/@aws-sdk/signature-v4-multi-region": { "node_modules/@aws-sdk/signature-v4-multi-region": {
"version": "3.996.34", "version": "3.996.39",
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.34.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz",
"integrity": "sha512-mx1L5qlumSOt/nKM3BFaHE2HVkWwz0i4Bw0pyYO42FfX/FeLlo8YI6csC0gSPprEk6fTIqI+CZN9RwUwKd5krQ==", "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/signature-v4": "^5.4.6", "@smithy/signature-v4": "^5.6.3",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -429,16 +341,16 @@
} }
}, },
"node_modules/@aws-sdk/token-providers": { "node_modules/@aws-sdk/token-providers": {
"version": "3.1066.0", "version": "3.1083.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1066.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz",
"integrity": "sha512-UqEUJq7dqa44hneLDUcX7UJy95cg8YqEWyakRpvIPnrNS3Mq+UlQHgCDGu5pvwAPtlIW4qcYbvW6reG6++FyvA==", "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/core": "^3.974.20", "@aws-sdk/core": "^3.975.1",
"@aws-sdk/nested-clients": "^3.997.20", "@aws-sdk/nested-clients": "^3.997.31",
"@aws-sdk/types": "^3.973.12", "@aws-sdk/types": "^3.974.0",
"@smithy/core": "^3.24.6", "@smithy/core": "^3.29.2",
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -446,24 +358,12 @@
} }
}, },
"node_modules/@aws-sdk/types": { "node_modules/@aws-sdk/types": {
"version": "3.973.12", "version": "3.974.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.12.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz",
"integrity": "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==", "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.14.3",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@aws-sdk/util-locate-window": {
"version": "3.965.7",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.7.tgz",
"integrity": "sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/types": "^4.16.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -471,13 +371,12 @@
} }
}, },
"node_modules/@aws-sdk/xml-builder": { "node_modules/@aws-sdk/xml-builder": {
"version": "3.972.29", "version": "3.972.34",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.29.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz",
"integrity": "sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==", "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/types": "^4.14.3", "@smithy/types": "^4.16.0",
"fast-xml-parser": "5.7.3",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -485,9 +384,9 @@
} }
}, },
"node_modules/@aws/lambda-invoke-store": { "node_modules/@aws/lambda-invoke-store": {
"version": "0.2.4", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
"integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
@@ -1565,18 +1464,6 @@
"@emnapi/runtime": "^1.7.1" "@emnapi/runtime": "^1.7.1"
} }
}, },
"node_modules/@nodable/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/nodable"
}
],
"license": "MIT"
},
"node_modules/@one-ini/wasm": { "node_modules/@one-ini/wasm": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
@@ -1695,13 +1582,12 @@
} }
}, },
"node_modules/@smithy/core": { "node_modules/@smithy/core": {
"version": "3.24.7", "version": "3.29.3",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.7.tgz", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.3.tgz",
"integrity": "sha512-KoUi4M1f3BG6kzN1FnCwL7oyFptTbyBJKjR6yhSib+JHRdUmM1o+VwsFtJ66NZCkCzVfJMWRHJNo0R0jznp0Pg==", "integrity": "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.16.1",
"@smithy/types": "^4.14.4",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -1709,13 +1595,13 @@
} }
}, },
"node_modules/@smithy/credential-provider-imds": { "node_modules/@smithy/credential-provider-imds": {
"version": "4.3.9", "version": "4.4.8",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.9.tgz", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.8.tgz",
"integrity": "sha512-ZlfJ/4Fa3jYb+3eaohPfG9utX9HmdhFNcFtpoGAhUhdynAOmGXtmigbi7eEiONKM+ykHw8RwKuDEb85Lx7t7fA==", "integrity": "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/core": "^3.24.7", "@smithy/core": "^3.29.3",
"@smithy/types": "^4.14.4", "@smithy/types": "^4.16.1",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -1723,39 +1609,27 @@
} }
}, },
"node_modules/@smithy/fetch-http-handler": { "node_modules/@smithy/fetch-http-handler": {
"version": "5.4.7", "version": "5.6.5",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.7.tgz", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.5.tgz",
"integrity": "sha512-NslaM2ir0N2hisDmzXLstPaVINZheh8SokyOC++kzFPloZucL2R7Y7bS57mSzx/1Fc/fqmn7twjkeezTTrV0EA==", "integrity": "sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/core": "^3.24.7", "@smithy/core": "^3.29.3",
"@smithy/types": "^4.14.4", "@smithy/types": "^4.16.1",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@smithy/is-array-buffer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/node-http-handler": { "node_modules/@smithy/node-http-handler": {
"version": "4.7.8", "version": "4.9.5",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.8.tgz", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz",
"integrity": "sha512-f+DbsWUwSbtMu1a/j8Y93KiU1SRg9nyzfjereqn1BJ33QOTUXxdlYvVXMhAYl1vuR1Kmna5aIJe09KSIfyFNYw==", "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/core": "^3.24.7", "@smithy/core": "^3.29.3",
"@smithy/types": "^4.14.4", "@smithy/types": "^4.16.1",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -1763,13 +1637,13 @@
} }
}, },
"node_modules/@smithy/signature-v4": { "node_modules/@smithy/signature-v4": {
"version": "5.4.7", "version": "5.6.4",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.7.tgz", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.4.tgz",
"integrity": "sha512-LwQZazFayImv+IOm0S0enoLeUJwmAlhGC5O6YCcLWezyu08dF46GOxPOq35OpBIHkgd7OvNvBStIFwVNyrvoBw==", "integrity": "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@smithy/core": "^3.24.7", "@smithy/core": "^3.29.3",
"@smithy/types": "^4.14.4", "@smithy/types": "^4.16.1",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
@@ -1777,9 +1651,9 @@
} }
}, },
"node_modules/@smithy/types": { "node_modules/@smithy/types": {
"version": "4.14.4", "version": "4.16.1",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.4.tgz", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
"integrity": "sha512-B2S9+UGm1+/pHkcx3ZoLVX1a+pmSk8rqxRR+ZsNqZaJ5q9FWX9AFGQVM4qG5+OBeQUZVy99HY8HqW8gK/wgXzQ==", "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": "^2.6.2" "tslib": "^2.6.2"
@@ -1788,32 +1662,6 @@
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@smithy/util-buffer-from": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/is-array-buffer": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-utf8": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/util-buffer-from": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tybys/wasm-util": { "node_modules/@tybys/wasm-util": {
"version": "0.10.3", "version": "0.10.3",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
@@ -2389,18 +2237,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/anynum": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz",
"integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/append-field": { "node_modules/append-field": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
@@ -2746,6 +2582,16 @@
"node-int64": "^0.4.0" "node-int64": "^0.4.0"
} }
}, },
"node_modules/buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"license": "MIT",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"node_modules/buffer-equal-constant-time": { "node_modules/buffer-equal-constant-time": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@ -3708,6 +3554,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/execa": { "node_modules/execa": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -3880,43 +3735,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-xml-builder": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz",
"integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"path-expression-matcher": "^1.5.0",
"xml-naming": "^0.1.0"
}
},
"node_modules/fast-xml-parser": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz",
"integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"@nodable/entities": "^2.1.0",
"fast-xml-builder": "^1.1.7",
"path-expression-matcher": "^1.5.0",
"strnum": "^2.2.3"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fb-watchman": { "node_modules/fb-watchman": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -4539,6 +4357,26 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore-by-default": { "node_modules/ignore-by-default": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
@@ -6445,21 +6283,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/path-expression-matcher": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
"integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/path-is-absolute": { "node_modules/path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -7376,6 +7199,16 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/stream-browserify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
"integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.4",
"readable-stream": "^3.5.0"
}
},
"node_modules/streamsearch": { "node_modules/streamsearch": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -7567,21 +7400,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/strnum": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz",
"integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"anynum": "^1.0.0"
}
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -8225,21 +8043,6 @@
"node": "^14.17.0 || ^16.13.0 || >=18.0.0" "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
} }
}, },
"node_modules/xml-naming": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
"integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+1
View File
@@ -16,6 +16,7 @@
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1068.0", "@aws-sdk/client-s3": "^3.1068.0",
"@aws-sdk/lib-storage": "^3.1085.0",
"@aws-sdk/s3-request-presigner": "^3.1068.0", "@aws-sdk/s3-request-presigner": "^3.1068.0",
"axios": "^1.16.0", "axios": "^1.16.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
+1
View File
@@ -12,6 +12,7 @@ router.get('/field-values', controller.getAssetFieldValues);
router.delete('/bulk', sensitiveOpsLimiter, controller.archiveAssets); router.delete('/bulk', sensitiveOpsLimiter, controller.archiveAssets);
router.delete('/bulk/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteAssets); router.delete('/bulk/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteAssets);
router.patch('/bulk-restore', controller.restoreAssets); router.patch('/bulk-restore', controller.restoreAssets);
router.get('/upload-progress/:uploadId', controller.streamUploadProgress);
// ─── Collection ─────────────────────────────────────────────────────────────── // ─── Collection ───────────────────────────────────────────────────────────────
router.get('/', controller.getAssets); router.get('/', controller.getAssets);
+21 -8
View File
@@ -22,7 +22,8 @@
// whenever S3_ENDPOINT isn't reachable from this machine — // whenever S3_ENDPOINT isn't reachable from this machine —
// see resolvePublicHost() below) // see resolvePublicHost() below)
const { S3Client, PutObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadBucketCommand } = require("@aws-sdk/client-s3"); const { S3Client, DeleteObjectCommand, GetObjectCommand, HeadBucketCommand } = require("@aws-sdk/client-s3");
const { Upload } = require("@aws-sdk/lib-storage");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const crypto = require("crypto"); const crypto = require("crypto");
const path = require("path"); const path = require("path");
@@ -127,7 +128,13 @@ async function buildPublicUrl(key, bucket = DEFAULT_BUCKET) {
// input: { buffer, originalname, mimetype, ownerType? } // input: { buffer, originalname, mimetype, ownerType? }
// output: { url, uuid } ← uuid = the S3 key, stored as storage_key in DB // output: { url, uuid } ← uuid = the S3 key, stored as storage_key in DB
// //
async function uploadFile({ buffer, originalname, mimetype, ownerType = "image" }) { // onProgress?: ({ loaded, total }) => void — real bytes-sent-to-Garage events,
// straight from the AWS SDK's own httpUploadProgress, not simulated. A plain
// PutObjectCommand (what this used to be) has no progress API at all; Upload
// auto-splits into multipart above its ~5MB partSize threshold, so large
// files (the case this actually matters for) report genuine incremental
// progress per part, while small ones just jump from 0 to 100 immediately.
async function uploadFile({ buffer, originalname, mimetype, ownerType = "image", onProgress }) {
if (!buffer) { if (!buffer) {
throw Object.assign(new Error("File buffer is required for S3 uploads."), { status: 400 }); throw Object.assign(new Error("File buffer is required for S3 uploads."), { status: 400 });
} }
@@ -135,12 +142,18 @@ async function uploadFile({ buffer, originalname, mimetype, ownerType = "image"
const bucket = DEFAULT_BUCKET; const bucket = DEFAULT_BUCKET;
const key = buildKey(originalname, ownerType); const key = buildKey(originalname, ownerType);
await s3.send(new PutObjectCommand({ const uploader = new Upload({
Bucket: bucket, client: s3,
Key: key, params: { Bucket: bucket, Key: key, Body: buffer, ContentType: mimetype },
Body: buffer, });
ContentType: mimetype,
})); if (onProgress) {
uploader.on("httpUploadProgress", (progress) => {
onProgress({ loaded: progress.loaded ?? 0, total: progress.total ?? buffer.length });
});
}
await uploader.done();
return { return {
url: await buildPublicUrl(key, bucket), url: await buildPublicUrl(key, bucket),
+52
View File
@@ -0,0 +1,52 @@
/***********************************************************************************************************************************************************************
* File Name: uploadProgress.service.js
* Type of Program: Service
* Description: In-memory SSE broadcaster for real upload progress on the
* Express -> S3 (Garage) leg of an asset upload. Keyed by a
* client-generated uploadId so the browser can open the stream
* before the upload request itself is even sent.
*
* No Redis — single-process only, same tradeoff already made by
* mediaToken.service.js's in-memory token cache. Fine for one
* instance; a second app instance would just never see progress
* for uploads routed to the other process.
*
* Author: Kenneth Obsequio (@lash0000)
***********************************************************************************************************************************************************************/
"use strict";
const clients = new Map(); // uploadId -> Response
function subscribe(uploadId, res) {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"X-Accel-Buffering": "no", // disable proxy-side buffering (nginx-style intermediaries)
});
res.write(": connected\n\n");
clients.set(uploadId, res);
res.on("close", () => {
if (clients.get(uploadId) === res) clients.delete(uploadId);
});
}
// No-ops if nobody's subscribed (client never opened the stream, or already
// disconnected) — progress is a best-effort visual, never load-bearing for
// the actual upload.
function publish(uploadId, data) {
const res = clients.get(uploadId);
if (!res) return;
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
function complete(uploadId, data = {}) {
const res = clients.get(uploadId);
if (!res) return;
res.write(`data: ${JSON.stringify({ ...data, done: true })}\n\n`);
res.end();
clients.delete(uploadId);
}
module.exports = { subscribe, publish, complete };
+16 -7
View File
@@ -18,9 +18,15 @@ const AUDIT_ID_FIELDS = new Set(["createdBy", "updatedBy", "deletedBy"]);
* Builds a Sequelize `where` clause from an array of filters. * Builds a Sequelize `where` clause from an array of filters.
* *
* @param {Array<{ id: string, value: any }>} filters * @param {Array<{ id: string, value: any }>} filters
* @param {Set<string>} allowedFields
* @param {string} [parentAlias] - main model's table alias (e.g. "User"). Needed
* to qualify Sequelize.col() references — unqualified col() refs throw
* "column reference is ambiguous" as soon as a query joins another table
* that happens to share a column name (e.g. Users + UserGroups both have
* is_active/createdAt/updatedAt/deletedAt).
* @returns {Object} Sequelize where clause * @returns {Object} Sequelize where clause
*/ */
function buildWhere(filters = [], allowedFields = new Set()) { function buildWhere(filters = [], allowedFields = new Set(), parentAlias = null) {
const where = []; const where = [];
for (const { id, value } of filters) { for (const { id, value } of filters) {
@@ -34,6 +40,8 @@ function buildWhere(filters = [], allowedFields = new Set()) {
continue; continue;
} }
const qualifiedCol = parentAlias ? `${parentAlias}.${id}` : id;
const conditions = values.map((v) => const conditions = values.map((v) =>
id.startsWith("personal_info.") id.startsWith("personal_info.")
? Sequelize.where( ? Sequelize.where(
@@ -41,7 +49,7 @@ function buildWhere(filters = [], allowedFields = new Set()) {
{ [Op.iLike]: `%${v}%` } { [Op.iLike]: `%${v}%` }
) )
: Sequelize.where( : Sequelize.where(
Sequelize.cast(Sequelize.col(id), "TEXT"), Sequelize.cast(Sequelize.col(qualifiedCol), "TEXT"),
{ [Op.iLike]: `%${v}%` } { [Op.iLike]: `%${v}%` }
) )
); );
@@ -59,8 +67,9 @@ function buildWhere(filters = [], allowedFields = new Set()) {
* @param {Array<{ id: string, desc: boolean }>} sort * @param {Array<{ id: string, desc: boolean }>} sort
* @returns {Array} Sequelize order clause * @returns {Array} Sequelize order clause
*/ */
function buildOrder(sort = [], allowedFields = new Set(), computedFields = new Set()) { function buildOrder(sort = [], allowedFields = new Set(), computedFields = new Set(), parentAlias = null) {
const order = []; const order = [];
const qualifiedId = (id) => (parentAlias ? `"${parentAlias}"."${id}"` : `"${id}"`);
for (const { id, desc } of sort) { for (const { id, desc } of sort) {
if (!id) continue; if (!id) continue;
@@ -83,7 +92,7 @@ function buildOrder(sort = [], allowedFields = new Set(), computedFields = new S
.map((val, i) => `WHEN '${val}' THEN ${i}`) .map((val, i) => `WHEN '${val}' THEN ${i}`)
.join(" "); .join(" ");
order.push([Sequelize.literal(`CASE "${id}" ${caseExpr} END`)]); order.push([Sequelize.literal(`CASE ${qualifiedId(id)} ${caseExpr} END`)]);
continue; continue;
} }
@@ -107,7 +116,7 @@ function buildOrder(sort = [], allowedFields = new Set(), computedFields = new S
* @param {Array} sort * @param {Array} sort
* @returns {{ where: Object, order: Array }} * @returns {{ where: Object, order: Array }}
*/ */
function buildQuery(filters = [], sort = [], allowedFields = [], computedFields = []) { function buildQuery(filters = [], sort = [], allowedFields = [], computedFields = [], parentAlias = null) {
const fieldSet = new Set(allowedFields); const fieldSet = new Set(allowedFields);
const computedSet = new Set(computedFields); const computedSet = new Set(computedFields);
const orderFieldSet = new Set([...allowedFields, ...computedFields]); const orderFieldSet = new Set([...allowedFields, ...computedFields]);
@@ -115,8 +124,8 @@ function buildQuery(filters = [], sort = [], allowedFields = [], computedFields
return { return {
// Computed (subquery/literal) columns aren't real table columns — filtering // Computed (subquery/literal) columns aren't real table columns — filtering
// via Sequelize.col() would error, so only allow them in ORDER BY, not WHERE. // via Sequelize.col() would error, so only allow them in ORDER BY, not WHERE.
where: buildWhere(filters, fieldSet), where: buildWhere(filters, fieldSet, parentAlias),
order: buildOrder(sort, orderFieldSet, computedSet), order: buildOrder(sort, orderFieldSet, computedSet, parentAlias),
}; };
} }
+13 -3
View File
@@ -1,5 +1,5 @@
// utils/paginate.util.js // utils/paginate.util.js
const { Sequelize } = require('sequelize'); const { Sequelize, Op } = require('sequelize');
const { modelToAttributes } = require('./modelToAttributes.util'); const { modelToAttributes } = require('./modelToAttributes.util');
const { excludeJsonbPaths } = require('./excludeJSONBPaths.util'); const { excludeJsonbPaths } = require('./excludeJSONBPaths.util');
const { buildQuery } = require('./buildQuery.util'); const { buildQuery } = require('./buildQuery.util');
@@ -108,7 +108,10 @@ async function paginate(model, req, {
const ALLOWED_FIELDS = attributes.map((a) => a.field); const ALLOWED_FIELDS = attributes.map((a) => a.field);
const computedFieldKeys = computedAttributes.map((c) => c.key); const computedFieldKeys = computedAttributes.map((c) => c.key);
const { where, order } = buildQuery(filters, sort, ALLOWED_FIELDS, computedFieldKeys); // Sequelize aliases the main model's table with the model's name by default
// (e.g. `FROM "users" AS "User"`) — needed to qualify Sequelize.col()
// references so they don't collide with same-named columns on joined tables.
const { where, order } = buildQuery(filters, sort, ALLOWED_FIELDS, computedFieldKeys, model.name);
// Build attribute includes: jsonb + audit subqueries + any extra from findOptions // Build attribute includes: jsonb + audit subqueries + any extra from findOptions
const baseIncludes = jsonbAttr ? [jsonbAttr] : []; const baseIncludes = jsonbAttr ? [jsonbAttr] : [];
@@ -131,9 +134,16 @@ async function paginate(model, req, {
const { attributes: _attr, ...restFindOptions } = findOptions; const { attributes: _attr, ...restFindOptions } = findOptions;
// Nest rather than shallow-spread — `where` and `restFindOptions.where` can
// both carry an [Op.and] key (the same global Symbol), and spreading two
// objects that share a Symbol key silently drops the first one's value.
const combinedWhere = restFindOptions.where
? { [Op.and]: [where, restFindOptions.where] }
: where;
const { count, rows } = await model.findAndCountAll({ const { count, rows } = await model.findAndCountAll({
...restFindOptions, ...restFindOptions,
where: { ...where, ...(restFindOptions.where ?? {}) }, where: combinedWhere,
order, order,
limit, limit,
offset, offset,