diff --git a/client/src/screens/browse/components/file-display/index.jsx b/client/src/screens/browse/components/file-display/index.jsx
index 0a83b8f..b0e0065 100644
--- a/client/src/screens/browse/components/file-display/index.jsx
+++ b/client/src/screens/browse/components/file-display/index.jsx
@@ -66,6 +66,8 @@ const FileDisplay = ({ file, path, code, isMobileView = false }) => {
const dispatch = useDispatch();
const preview_url = file.webUrl;
+ const thumbnailUrl =
+ typeof file.thumbnail === "string" ? file.thumbnail : file.thumbnail?.url;
const handleDownload = async () => {
if (!isLoggedIn) {
@@ -147,7 +149,7 @@ const FileDisplay = ({ file, path, code, isMobileView = false }) => {
}`}
>
{
async function thumbnailrefresh() {
@@ -161,7 +163,7 @@ const FileDisplay = ({ file, path, code, isMobileView = false }) => {
diff --git a/server/.env.example b/server/.env.example
index a1489fd..4fe85cc 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -18,3 +18,8 @@ API_BASE_URL=
# OneDrive storage
ONEDRIVE_FOLDER_ID=
+
+# ImageKit — get from ImageKit dashboard > Developer Options
+IMAGEKIT_PUBLIC_KEY=
+IMAGEKIT_PRIVATE_KEY=
+IMAGEKIT_URL_ENDPOINT=
diff --git a/server/modules/course/course.model.js b/server/modules/course/course.model.js
index 7e7bc69..3d1893d 100644
--- a/server/modules/course/course.model.js
+++ b/server/modules/course/course.model.js
@@ -13,7 +13,11 @@ const FileSchema = Schema({
name: { type: String, required: true },
fileId: { type: String, required: true },
size: { type: String, required: true },
- thumbnail: { type: String },
+ thumbnail: {
+ url: { type: String },
+ fileId: { type: String },
+ path: { type: String },
+ },
webUrl: { type: String, required: true },
downloadUrl: { type: String, required: true },
isVerified: { type: Boolean, default: false, required: true },
diff --git a/server/modules/onedrive/onedrive.controller.js b/server/modules/onedrive/onedrive.controller.js
index 014dedf..4479f15 100644
--- a/server/modules/onedrive/onedrive.controller.js
+++ b/server/modules/onedrive/onedrive.controller.js
@@ -5,6 +5,7 @@ import settings from "../../config/onedrive.js";
import fs from "fs";
import { extractGraphErrorDetails, formatGraphErrorMessage } from "../../utils/graphError.js";
import { normalizeCourseCode, getCourseCodeCaseInsensitiveRegex } from "../../utils/course.js";
+import { uploadThumbnail, isImageKitUrl } from "../../services/imagekit.js";
import CourseModel, { FolderModel, FileModel } from "../course/course.model.js";
import SearchResults from "../search/search.model.js";
@@ -79,16 +80,46 @@ export async function getCourseIds(req, res) {
export async function thumbnail(req, res) {
const fileId = req.body.fileId;
- const thumbnaillink = `https://graph.microsoft.com/v1.0/me/drive/items/${fileId}/thumbnails`;
+
+ // 1. Already a permanent ImageKit URL in DB — return immediately
+ const file = await FileModel.findOne({ fileId }).select("thumbnail");
+ const storedThumbnailUrl =
+ typeof file?.thumbnail === "string"
+ ? file.thumbnail
+ : file?.thumbnail?.url;
+
+ if (storedThumbnailUrl && isImageKitUrl(storedThumbnailUrl)) {
+ return res.status(200).json(storedThumbnailUrl);
+ }
+
+ // 2. Fetch a fresh temporary URL from Graph API (legacy files with expired OneDrive URLs)
const access_token = await getAccessToken();
- const thumbnaildata = await axios.get(thumbnaillink, {
- headers: {
- Authorization: `Bearer ${access_token}`,
- },
- });
+ const thumbnaildata = await axios.get(
+ `https://graph.microsoft.com/v1.0/me/drive/items/${fileId}/thumbnails`,
+ { headers: { Authorization: `Bearer ${access_token}` } }
+ );
const thumbnailurl = thumbnaildata.data.value?.[0]?.medium?.url;
- await FileModel.updateOne({ fileId }, { $set: { thumbnail: thumbnailurl } });
- return res.status(200).json(thumbnailurl);
+ if (!thumbnailurl) throw new AppError(404, "Thumbnail not found");
+
+ // 3. Download raw image bytes and upload to ImageKit as WebP permanently
+ const imgResponse = await axios.get(thumbnailurl, { responseType: "arraybuffer" });
+ const { url: permanentUrl, fileId: imagekitFileId, path: imagekitPath } = await uploadThumbnail(fileId, Buffer.from(imgResponse.data));
+
+ // 4. Persist permanent URL to DB so this file never hits Graph API again
+ await FileModel.updateOne(
+ { fileId },
+ {
+ $set: {
+ thumbnail: {
+ url: permanentUrl,
+ fileId: imagekitFileId,
+ path: imagekitPath,
+ },
+ },
+ }
+ );
+
+ return res.status(200).json(permanentUrl);
}
export async function getFile(req, res) {
@@ -281,7 +312,9 @@ async function visitFile(file, currCourse) {
name: file.name,
id: file.id,
size: file.size * 0.000001,
- thumbnail: file?.thumbnails?.[0]?.medium?.url || "null",
+ thumbnail: {
+ url: file?.thumbnails?.[0]?.medium?.url || "null",
+ },
});
return NewFile._id;
}
diff --git a/server/package-lock.json b/server/package-lock.json
index f890367..b2568da 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@azure/identity": "^3.0.0",
+ "@imagekit/nodejs": "^7.5.0",
"@microsoft/microsoft-graph-client": "^3.0.2",
"aes-js": "^3.1.2",
"axios": "^1.1.3",
@@ -25,7 +26,7 @@
"formidable": "^2.1.1",
"isomorphic-fetch": "^3.0.0",
"joi": "^17.7.1",
- "jsonwebtoken": "^8.5.1",
+ "jsonwebtoken": "^9.0.3",
"mongoose": "^6.7.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.1",
@@ -1144,25 +1145,6 @@
"node": ">=12.0.0"
}
},
- "node_modules/@azure/identity/node_modules/jwa": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
- "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
- "dependencies": {
- "buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/@azure/identity/node_modules/jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "dependencies": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- },
"node_modules/@azure/logger": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz",
@@ -1206,6 +1188,55 @@
"node": "10 || 12 || 14 || 16 || 18"
}
},
+ "node_modules/@azure/msal-node/node_modules/jsonwebtoken": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
+ },
+ "engines": {
+ "node": ">=4",
+ "npm": ">=1.4.28"
+ }
+ },
+ "node_modules/@azure/msal-node/node_modules/jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/@azure/msal-node/node_modules/jws": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
+ "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.2",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/@azure/msal-node/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
"node_modules/@babel/runtime": {
"version": "7.20.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.0.tgz",
@@ -1461,6 +1492,15 @@
"@hapi/hoek": "^9.0.0"
}
},
+ "node_modules/@imagekit/nodejs": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@imagekit/nodejs/-/nodejs-7.5.0.tgz",
+ "integrity": "sha512-o87dE4/4CtGomeMNGWmFxSKcQeUjb2VzY3k2L5peGgA3kpvQph3r5hBkvl5ZEOlHbIQvTFqf/wTPBRE5u2ixNg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "standardwebhooks": "^1.0.0"
+ }
+ },
"node_modules/@js-sdsl/ordered-map": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
@@ -1635,6 +1675,12 @@
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
+ "node_modules/@stablelib/base64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
+ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
+ "license": "MIT"
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -1982,13 +2028,14 @@
}
},
"node_modules/axios": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
- "integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==",
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz",
+ "integrity": "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==",
+ "license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.0",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
}
},
"node_modules/balanced-match": {
@@ -2137,7 +2184,8 @@
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
@@ -2729,7 +2777,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "optional": true,
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
@@ -2864,6 +2911,12 @@
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
+ "node_modules/fast-sha256": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
+ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
+ "license": "Unlicense"
+ },
"node_modules/fast-xml-parser": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz",
@@ -2944,43 +2997,6 @@
"@google-cloud/storage": "^7.14.0"
}
},
- "node_modules/firebase-admin/node_modules/jsonwebtoken": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
- "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
- "dependencies": {
- "jws": "^3.2.2",
- "lodash.includes": "^4.3.0",
- "lodash.isboolean": "^3.0.3",
- "lodash.isinteger": "^4.0.4",
- "lodash.isnumber": "^3.0.3",
- "lodash.isplainobject": "^4.0.6",
- "lodash.isstring": "^4.0.1",
- "lodash.once": "^4.0.0",
- "ms": "^2.1.1",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=12",
- "npm": ">=6"
- }
- },
- "node_modules/firebase-admin/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/firebase-admin/node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/firebase-admin/node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -2994,15 +3010,16 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
- "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
+ "license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -3013,12 +3030,15 @@
}
},
"node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -3319,25 +3339,6 @@
"node": ">=14"
}
},
- "node_modules/google-auth-library/node_modules/jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/google-auth-library/node_modules/jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "dependencies": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- },
"node_modules/google-gax": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz",
@@ -3405,25 +3406,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/gtoken/node_modules/jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/gtoken/node_modules/jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "dependencies": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -3448,7 +3430,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "optional": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
@@ -3826,11 +3807,12 @@
}
},
"node_modules/jsonwebtoken": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
- "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
"dependencies": {
- "jws": "^3.2.2",
+ "jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
@@ -3839,11 +3821,11 @@
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
- "semver": "^5.6.0"
+ "semver": "^7.5.4"
},
"engines": {
- "node": ">=4",
- "npm": ">=1.4.28"
+ "node": ">=12",
+ "npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/ms": {
@@ -3851,12 +3833,25 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
+ "node_modules/jsonwebtoken/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/jwa": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
- "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
"dependencies": {
- "buffer-equal-constant-time": "1.0.1",
+ "buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
@@ -3899,11 +3894,12 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
"dependencies": {
- "jwa": "^1.4.1",
+ "jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
@@ -4670,9 +4666,13 @@
}
},
"node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
},
"node_modules/pstree.remy": {
"version": "1.1.8",
@@ -5065,6 +5065,16 @@
"node": ">= 10.x"
}
},
+ "node_modules/standardwebhooks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
+ "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@stablelib/base64": "^1.0.0",
+ "fast-sha256": "^1.3.0"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -6425,27 +6435,6 @@
"stoppable": "^1.1.0",
"tslib": "^2.2.0",
"uuid": "^8.3.0"
- },
- "dependencies": {
- "jwa": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
- "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
- "requires": {
- "buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "requires": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- }
}
},
"@azure/logger": {
@@ -6477,6 +6466,49 @@
"@azure/msal-common": "^7.6.0",
"jsonwebtoken": "^8.5.1",
"uuid": "^8.3.0"
+ },
+ "dependencies": {
+ "jsonwebtoken": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "jwa": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+ "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "requires": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
+ "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
+ "requires": {
+ "jwa": "^1.4.2",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ }
}
},
"@babel/runtime": {
@@ -6678,6 +6710,14 @@
"@hapi/hoek": "^9.0.0"
}
},
+ "@imagekit/nodejs": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@imagekit/nodejs/-/nodejs-7.5.0.tgz",
+ "integrity": "sha512-o87dE4/4CtGomeMNGWmFxSKcQeUjb2VzY3k2L5peGgA3kpvQph3r5hBkvl5ZEOlHbIQvTFqf/wTPBRE5u2ixNg==",
+ "requires": {
+ "standardwebhooks": "^1.0.0"
+ }
+ },
"@js-sdsl/ordered-map": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
@@ -6815,6 +6855,11 @@
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
+ "@stablelib/base64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
+ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="
+ },
"@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
@@ -7121,13 +7166,13 @@
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="
},
"axios": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
- "integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==",
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz",
+ "integrity": "sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==",
"requires": {
- "follow-redirects": "^1.15.0",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
}
},
"balanced-match": {
@@ -7671,7 +7716,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "optional": true,
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
@@ -7779,6 +7823,11 @@
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
+ "fast-sha256": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
+ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="
+ },
"fast-xml-parser": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz",
@@ -7838,33 +7887,6 @@
"uuid": "^11.0.2"
},
"dependencies": {
- "jsonwebtoken": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
- "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
- "requires": {
- "jws": "^3.2.2",
- "lodash.includes": "^4.3.0",
- "lodash.isboolean": "^3.0.3",
- "lodash.isinteger": "^4.0.4",
- "lodash.isnumber": "^3.0.3",
- "lodash.isplainobject": "^4.0.6",
- "lodash.isstring": "^4.0.1",
- "lodash.once": "^4.0.0",
- "ms": "^2.1.1",
- "semver": "^7.5.4"
- }
- },
- "ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="
- },
"uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -7873,17 +7895,19 @@
}
},
"follow-redirects": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
- "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="
},
"form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
"mime-types": "^2.1.12"
}
},
@@ -8103,27 +8127,6 @@
"gcp-metadata": "^6.1.0",
"gtoken": "^7.0.0",
"jws": "^4.0.0"
- },
- "dependencies": {
- "jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "requires": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "requires": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- }
}
},
"google-gax": {
@@ -8171,27 +8174,6 @@
"requires": {
"gaxios": "^6.0.0",
"jws": "^4.0.0"
- },
- "dependencies": {
- "jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
- "requires": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "requires": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- }
}
},
"has-flag": {
@@ -8209,7 +8191,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "optional": true,
"requires": {
"has-symbols": "^1.0.3"
}
@@ -8477,11 +8458,11 @@
}
},
"jsonwebtoken": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
- "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"requires": {
- "jws": "^3.2.2",
+ "jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
@@ -8490,22 +8471,27 @@
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
- "semver": "^5.6.0"
+ "semver": "^7.5.4"
},
"dependencies": {
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="
}
}
},
"jwa": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
- "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"requires": {
- "buffer-equal-constant-time": "1.0.1",
+ "buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
@@ -8539,11 +8525,11 @@
}
},
"jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"requires": {
- "jwa": "^1.4.1",
+ "jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
@@ -9139,9 +9125,9 @@
}
},
"proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="
},
"pstree.remy": {
"version": "1.1.8",
@@ -9434,6 +9420,15 @@
"resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
"integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ=="
},
+ "standardwebhooks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
+ "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
+ "requires": {
+ "@stablelib/base64": "^1.0.0",
+ "fast-sha256": "^1.3.0"
+ }
+ },
"statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
diff --git a/server/package.json b/server/package.json
index e61416f..56c3895 100644
--- a/server/package.json
+++ b/server/package.json
@@ -15,6 +15,7 @@
"license": "ISC",
"dependencies": {
"@azure/identity": "^3.0.0",
+ "@imagekit/nodejs": "^7.5.0",
"@microsoft/microsoft-graph-client": "^3.0.2",
"aes-js": "^3.1.2",
"axios": "^1.1.3",
@@ -30,7 +31,7 @@
"formidable": "^2.1.1",
"isomorphic-fetch": "^3.0.0",
"joi": "^17.7.1",
- "jsonwebtoken": "^8.5.1",
+ "jsonwebtoken": "^9.0.3",
"mongoose": "^6.7.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.1",
diff --git a/server/services/UploadFile.js b/server/services/UploadFile.js
index 4ed0bb9..107fa9d 100644
--- a/server/services/UploadFile.js
+++ b/server/services/UploadFile.js
@@ -5,6 +5,7 @@ import { FileModel } from "../modules/course/course.model.js";
import Contribution from "../modules/contribution/contribution.model.js";
import logger from "../utils/logger.js";
import { logGraphError } from "../utils/graphError.js";
+import { uploadThumbnail, deleteThumbnail } from "./imagekit.js";
const parent_item_id = process.env.ONEDRIVE_FOLDER_ID;
@@ -92,37 +93,62 @@ async function UploadFile(contributionId, filePath, fileName) {
const { data } = await axios.put(url, file, config);
const createurllink = `https://graph.microsoft.com/v1.0/me/drive/items/${data.id}/createLink`;
const thumbnaillink = `https://graph.microsoft.com/v1.0/me/drive/items/${data.id}/thumbnails`;
- const urldata = await axios.post(
- createurllink,
- {
- type: "view",
- scope: "organization",
- },
- {
- headers: {
- Authorization: `Bearer ${access_token}`,
- "Content-Type": "application/json",
- },
- }
- );
- const thumbnaildata = await axios.get(thumbnaillink, {
- headers: {
- Authorization: `Bearer ${access_token}`,
- },
- });
- const thumbnailurl = thumbnaildata.data.value?.[0]?.medium?.url;
+
+ // Run createLink and thumbnail fetch in parallel
+ const [urldata, thumbnaildata] = await Promise.all([
+ axios.post(
+ createurllink,
+ { type: "view", scope: "organization" },
+ {
+ headers: {
+ Authorization: `Bearer ${access_token}`,
+ "Content-Type": "application/json",
+ },
+ }
+ ),
+ axios.get(thumbnaillink, {
+ headers: { Authorization: `Bearer ${access_token}` },
+ }),
+ ]);
+
+ const tempThumbnailUrl = thumbnaildata.data.value?.[0]?.medium?.url;
const webUrl = urldata?.data?.link?.webUrl;
+
const fileData = new FileModel({
isVerified: !!existingContribution?.approved,
fileId: data.id,
size: data.size,
- thumbnail: thumbnailurl,
+ thumbnail: tempThumbnailUrl ? { url: tempThumbnailUrl } : undefined,
name: fileName,
downloadUrl: `${webUrl}?download=1`,
webUrl: webUrl,
});
await fileData.save();
logger.info("File saved");
+
+ // Upload thumbnail to ImageKit in the background — don't block the response
+ if (tempThumbnailUrl) {
+ (async () => {
+ try {
+ const imgResponse = await axios.get(tempThumbnailUrl, { responseType: "arraybuffer" });
+ const { url: permanentUrl, fileId: imagekitFileId, path: imagekitPath } = await uploadThumbnail(data.id, Buffer.from(imgResponse.data));
+ await FileModel.updateOne(
+ { fileId: data.id },
+ {
+ thumbnail: {
+ url: permanentUrl,
+ fileId: imagekitFileId,
+ path: imagekitPath,
+ },
+ }
+ );
+ logger.info(`ImageKit thumbnail stored for ${data.id}`);
+ } catch (thumbErr) {
+ logger.warn(`ImageKit thumbnail upload failed for ${data.id}: ${thumbErr.message}`);
+ }
+ })();
+ }
+
return fileData._id;
} catch (error) {
logGraphError(logger, error, "Failed to upload file to Microsoft Graph");
@@ -133,6 +159,16 @@ async function UploadFile(contributionId, filePath, fileName) {
async function DeleteFile(fileId) {
const access_token = await getAccessToken();
+ // Delete ImageKit thumbnail in the background — don't block the response
+ FileModel.findOne({ fileId }).lean().then((fileDoc) => {
+ const imagekitId = fileDoc?.thumbnail?.fileId || fileDoc?.imagekitFileId;
+ if (imagekitId) {
+ deleteThumbnail(imagekitId).catch((ikErr) => {
+ logger.warn(`ImageKit thumbnail deletion failed for ${fileId}: ${ikErr.message}`);
+ });
+ }
+ }).catch(() => {});
+
try {
//obtain parent folder onedrive id
const { data } = await axios.get(
diff --git a/server/services/imagekit.js b/server/services/imagekit.js
new file mode 100644
index 0000000..2c8b8a5
--- /dev/null
+++ b/server/services/imagekit.js
@@ -0,0 +1,56 @@
+import ImageKit, { toFile } from "@imagekit/nodejs";
+
+// Lazy singleton — deferred until first use so env vars are loaded
+let _ik = null;
+function getClient() {
+ if (!_ik) {
+ if (!process.env.IMAGEKIT_PUBLIC_KEY || !process.env.IMAGEKIT_PRIVATE_KEY || !process.env.IMAGEKIT_URL_ENDPOINT) {
+ throw new Error("IMAGEKIT_PUBLIC_KEY, IMAGEKIT_PRIVATE_KEY and IMAGEKIT_URL_ENDPOINT must be set in .env");
+ }
+ _ik = new ImageKit({
+ publicKey: process.env.IMAGEKIT_PUBLIC_KEY,
+ privateKey: process.env.IMAGEKIT_PRIVATE_KEY,
+ urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT,
+ });
+ }
+ return _ik;
+}
+
+/**
+ * Upload a raw image buffer to ImageKit as WebP and return the permanent URL.
+ * Uses fileId as the filename so re-uploads overwrite the existing file.
+ * ImageKit handles WebP conversion at the CDN edge — no sharp needed.
+ */
+export async function uploadThumbnail(fileId, imageBuffer) {
+ const fileObject = await toFile(imageBuffer, `${fileId}.webp`, { type: "image/webp" });
+ const response = await getClient().files.upload({
+ file: fileObject,
+ fileName: `${fileId}.webp`,
+ folder: "/thumbnails",
+ useUniqueFileName: false, // overwrite same fileId on re-upload
+ transformation: {
+ pre: "f-webp,q-80", // convert to WebP quality 80 at edge
+ },
+ });
+ return {
+ url: response.url,
+ fileId: response.fileId,
+ path: response.filePath || `/thumbnails/${fileId}.webp`,
+ };
+}
+
+/**
+ * Delete a thumbnail from ImageKit by its internal fileId.
+ */
+export async function deleteThumbnail(imagekitFileId) {
+ await getClient().files.delete(imagekitFileId);
+}
+
+/**
+ * Returns true if the URL is already a permanent ImageKit URL.
+ */
+export function isImageKitUrl(url) {
+ return Boolean(url && process.env.IMAGEKIT_URL_ENDPOINT && url.startsWith(process.env.IMAGEKIT_URL_ENDPOINT));
+}
+
+export default getClient;
diff --git a/server/yarn.lock b/server/yarn.lock
index e8dfe55..7e7de00 100644
--- a/server/yarn.lock
+++ b/server/yarn.lock
@@ -954,6 +954,13 @@
dependencies:
"@hapi/hoek" "^9.0.0"
+"@imagekit/nodejs@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.npmjs.org/@imagekit/nodejs/-/nodejs-7.5.0.tgz"
+ integrity sha512-o87dE4/4CtGomeMNGWmFxSKcQeUjb2VzY3k2L5peGgA3kpvQph3r5hBkvl5ZEOlHbIQvTFqf/wTPBRE5u2ixNg==
+ dependencies:
+ standardwebhooks "^1.0.0"
+
"@js-sdsl/ordered-map@^4.4.2":
version "4.4.2"
resolved "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz"
@@ -1057,6 +1064,11 @@
resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz"
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
+"@stablelib/base64@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz"
+ integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==
+
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz"
@@ -1300,13 +1312,13 @@ atomic-sleep@^1.0.0:
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
axios@^1.1.3:
- version "1.1.3"
- resolved "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz"
- integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==
+ version "1.15.1"
+ resolved "https://registry.npmjs.org/axios/-/axios-1.15.1.tgz"
+ integrity sha512-WOG+Jj8ZOvR0a3rAn+Tuf1UQJRxw5venr6DgdbJzngJE3qG7X0kL83CZGpdHMxEm+ZK3seAbvFsw4FfOfP9vxg==
dependencies:
- follow-redirects "^1.15.0"
- form-data "^4.0.0"
- proxy-from-env "^1.1.0"
+ follow-redirects "^1.15.11"
+ form-data "^4.0.5"
+ proxy-from-env "^2.1.0"
balanced-match@^1.0.0:
version "1.0.2"
@@ -1393,7 +1405,7 @@ bson@^4.6.5, bson@^4.7.0:
dependencies:
buffer "^5.6.0"
-buffer-equal-constant-time@^1.0.1, buffer-equal-constant-time@1.0.1:
+buffer-equal-constant-time@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
@@ -1914,6 +1926,11 @@ fast-safe-stringify@^2.1.1:
resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
+fast-sha256@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz"
+ integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==
+
fast-xml-parser@^4.4.1:
version "4.5.3"
resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz"
@@ -1974,10 +1991,10 @@ firebase-admin@^13.4.0:
"@google-cloud/firestore" "^7.11.0"
"@google-cloud/storage" "^7.14.0"
-follow-redirects@^1.15.0:
- version "1.15.2"
- resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
- integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
+follow-redirects@^1.15.11:
+ version "1.16.0"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz"
+ integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==
form-data@^2.5.5:
version "2.5.5"
@@ -1991,13 +2008,15 @@ form-data@^2.5.5:
mime-types "^2.1.35"
safe-buffer "^5.2.1"
-form-data@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
- integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+form-data@^4.0.0, form-data@^4.0.5:
+ version "4.0.5"
+ resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz"
+ integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
+ es-set-tostringtag "^2.1.0"
+ hasown "^2.0.2"
mime-types "^2.1.12"
formidable@^2.1.1:
@@ -2425,12 +2444,12 @@ jsonwebtoken@^8.5.1:
ms "^2.1.1"
semver "^5.6.0"
-jsonwebtoken@^9.0.0:
- version "9.0.2"
- resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz"
- integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
+jsonwebtoken@^9.0.0, jsonwebtoken@^9.0.3:
+ version "9.0.3"
+ resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz"
+ integrity sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==
dependencies:
- jws "^3.2.2"
+ jws "^4.0.1"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
@@ -2441,21 +2460,21 @@ jsonwebtoken@^9.0.0:
ms "^2.1.1"
semver "^7.5.4"
-jwa@^1.4.1:
- version "1.4.1"
- resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz"
- integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+jwa@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz"
+ integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==
dependencies:
- buffer-equal-constant-time "1.0.1"
+ buffer-equal-constant-time "^1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
-jwa@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz"
- integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
+jwa@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz"
+ integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==
dependencies:
- buffer-equal-constant-time "1.0.1"
+ buffer-equal-constant-time "^1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
@@ -2472,19 +2491,19 @@ jwks-rsa@^3.1.0:
lru-memoizer "^2.2.0"
jws@^3.2.2:
- version "3.2.2"
- resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz"
- integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+ version "3.2.3"
+ resolved "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz"
+ integrity sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==
dependencies:
- jwa "^1.4.1"
+ jwa "^1.4.2"
safe-buffer "^5.0.1"
-jws@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz"
- integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
+jws@^4.0.0, jws@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz"
+ integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==
dependencies:
- jwa "^2.0.0"
+ jwa "^2.0.1"
safe-buffer "^5.0.1"
kareem@2.4.1:
@@ -3013,10 +3032,10 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
-proxy-from-env@^1.1.0:
- version "1.1.0"
- resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
- integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+proxy-from-env@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz"
+ integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==
pstree.remy@^1.1.8:
version "1.1.8"
@@ -3197,9 +3216,9 @@ semver@^7.3.5:
lru-cache "^6.0.0"
semver@^7.5.4:
- version "7.7.2"
- resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz"
- integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
+ version "7.7.4"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz"
+ integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
semver@~7.0.0:
version "7.0.0"
@@ -3303,6 +3322,14 @@ split2@^4.0.0:
resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz"
integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==
+standardwebhooks@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz"
+ integrity sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==
+ dependencies:
+ "@stablelib/base64" "^1.0.0"
+ fast-sha256 "^1.3.0"
+
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"