55 * the latest URLs from MediaLit service using the stored mediaId.
66 *
77 * Usage:
8- * pnpm media:refresh [domain-name] [--discover ]
8+ * pnpm media:refresh [domain-name] [--save ]
99 *
1010 * Options:
11- * --discover Only print all media objects found, without fetching or updating
11+ * --save Actually update the database (Default is DRY RUN / DISCOVER)
1212 *
1313 * If domain-name is provided, only that domain's media is refreshed.
1414 * If omitted, all domains are processed.
1515 *
1616 * Environment variables required:
1717 * - DB_CONNECTION_STRING: MongoDB connection string
18- * - MEDIALIT_SERVER: MediaLit API server URL (not required for --discover)
19- * - MEDIALIT_APIKEY: MediaLit API key (not required for --discover)
18+ * - MEDIALIT_SERVER: MediaLit API server URL
19+ * - MEDIALIT_APIKEY: MediaLit API key
2020 */
2121
2222import mongoose from "mongoose" ;
@@ -51,19 +51,17 @@ loadEnvFile();
5151
5252// Parse command line arguments
5353const args = process . argv . slice ( 2 ) ;
54- const discoverMode = args . includes ( "--discover" ) ;
54+ const saveMode = args . includes ( "--save" ) ;
55+ const discoverMode = ! saveMode ;
5556const domainArg = args . find ( ( arg ) => ! arg . startsWith ( "--" ) ) ;
5657
5758if ( ! process . env . DB_CONNECTION_STRING ) {
5859 throw new Error ( "DB_CONNECTION_STRING is not set" ) ;
5960}
6061
61- if (
62- ! discoverMode &&
63- ( ! process . env . MEDIALIT_SERVER || ! process . env . MEDIALIT_APIKEY )
64- ) {
62+ if ( ! process . env . MEDIALIT_SERVER || ! process . env . MEDIALIT_APIKEY ) {
6563 throw new Error (
66- "MEDIALIT_SERVER and MEDIALIT_APIKEY must be set (not required for --discover mode) " ,
64+ "MEDIALIT_SERVER and MEDIALIT_APIKEY must be set to fetch refreshed URLs " ,
6765 ) ;
6866}
6967
@@ -89,6 +87,29 @@ const stats = {
8987// Cache to avoid duplicate API calls for the same mediaId
9088const mediaCache = new Map < string , Media | null > ( ) ;
9189
90+ /**
91+ * Extracts Media ID from a MediaLit URL
92+ */
93+ function extractIdFromUrl ( url : string ) : string | null {
94+ try {
95+ const { pathname } = new URL ( url ) ;
96+ const segments = pathname . split ( "/" ) . filter ( Boolean ) ;
97+
98+ if ( segments . length < 2 ) {
99+ return null ;
100+ }
101+
102+ const lastSegment = segments [ segments . length - 1 ] ;
103+ if ( ! / ^ m a i n \. [ ^ / ] + $ / i. test ( lastSegment ) ) {
104+ return null ;
105+ }
106+
107+ return segments [ segments . length - 2 ] || null ;
108+ } catch {
109+ return null ;
110+ }
111+ }
112+
92113/**
93114 * Fetch fresh media data from MediaLit
94115 */
@@ -124,12 +145,32 @@ async function refreshMediaObject(
124145
125146 stats . processed ++ ;
126147
127- // In discover mode, just print and return null (no update)
148+ // In discover mode, fetch and print comparison but return null
128149 if ( discoverMode ) {
129150 console . log ( ` 📎 ${ context || "Media" } : ${ existingMedia . mediaId } ` ) ;
130- console . log ( ` File: ${ existingMedia . file || "(none)" } ` ) ;
151+ console . log (
152+ ` 📄 Current File: ${ existingMedia . file || "(none)" } ` ,
153+ ) ;
131154 if ( existingMedia . thumbnail ) {
132- console . log ( ` Thumb: ${ existingMedia . thumbnail } ` ) ;
155+ console . log ( ` 🖼️ Current Thumb: ${ existingMedia . thumbnail } ` ) ;
156+ }
157+
158+ const freshMedia = await fetchMediaFromMediaLit ( existingMedia . mediaId ) ;
159+ if ( freshMedia ) {
160+ if ( freshMedia . file !== existingMedia . file ) {
161+ console . log ( ` ✨ New File: ${ freshMedia . file } ` ) ;
162+ }
163+ if ( freshMedia . thumbnail !== existingMedia . thumbnail ) {
164+ console . log ( ` ✨ New Thumb: ${ freshMedia . thumbnail } ` ) ;
165+ }
166+ if (
167+ freshMedia . file === existingMedia . file &&
168+ freshMedia . thumbnail === existingMedia . thumbnail
169+ ) {
170+ console . log ( ` ✅ URLs are already up to date` ) ;
171+ }
172+ } else {
173+ console . log ( ` ❌ Could not fetch updated URLs from MediaLit` ) ;
133174 }
134175 return null ;
135176 }
@@ -206,7 +247,7 @@ async function recursiveMediaRefresh(obj: any): Promise<boolean> {
206247 if ( Object . prototype . hasOwnProperty . call ( obj , key ) ) {
207248 const value = obj [ key ] ;
208249
209- // Special handling for stringified JSON fields
250+ // 1. Special handling for stringified JSON fields (e.g. ProseMirror docs)
210251 if (
211252 typeof value === "string" &&
212253 ( ( value . trim ( ) . startsWith ( "{" ) && value . trim ( ) . endsWith ( "}" ) ) ||
@@ -220,12 +261,63 @@ async function recursiveMediaRefresh(obj: any): Promise<boolean> {
220261 obj [ key ] = JSON . stringify ( parsed ) ;
221262 updated = true ;
222263 }
223- continue ; // Skip normal recursion for this key
264+ continue ; // Skip normal recursion/string check for this key
224265 } catch {
225266 // Not valid JSON
226267 }
227268 }
228269
270+ // 2. If the value is a string, check if it's a MediaLit URL that needs refreshing
271+ if ( typeof value === "string" ) {
272+ const extractedId = extractIdFromUrl ( value ) ;
273+ if ( extractedId ) {
274+ stats . processed ++ ;
275+ if ( discoverMode ) {
276+ console . log (
277+ ` 📎 URL found in key '${ key } ': ${ extractedId } ` ,
278+ ) ;
279+ console . log ( ` 🔗 Current Value: ${ value } ` ) ;
280+
281+ const freshMedia =
282+ await fetchMediaFromMediaLit ( extractedId ) ;
283+ if ( freshMedia ) {
284+ if ( freshMedia . file !== value ) {
285+ console . log (
286+ ` ✨ New Value: ${ freshMedia . file } ` ,
287+ ) ;
288+ } else {
289+ console . log (
290+ ` ✅ Value is already up to date` ,
291+ ) ;
292+ }
293+ } else {
294+ console . log (
295+ ` ❌ Could not fetch updated URL from MediaLit` ,
296+ ) ;
297+ }
298+ continue ;
299+ }
300+
301+ const freshMedia =
302+ await fetchMediaFromMediaLit ( extractedId ) ;
303+ if ( freshMedia && freshMedia . file !== value ) {
304+ // Check if we should use file or thumbnail URL
305+ // Usually for 'src' we use file URL.
306+ // If the current URL ends with main.png (or similar), we update it to freshMedia.file
307+ // Note: Our extractIdFromUrl only matches main images anyway.
308+ obj [ key ] = freshMedia . file ;
309+ updated = true ;
310+ stats . updated ++ ;
311+ } else if ( ! freshMedia ) {
312+ stats . failed ++ ;
313+ } else {
314+ stats . skipped ++ ;
315+ }
316+ continue ;
317+ }
318+ }
319+
320+ // 3. Normal recursion for objects/arrays
229321 const result = await recursiveMediaRefresh ( value ) ;
230322 if ( result ) {
231323 updated = true ;
@@ -290,8 +382,17 @@ async function refreshCourseMedia(domainId: mongoose.Types.ObjectId) {
290382 }
291383
292384 if ( hasUpdates ) {
293- await CourseModel . updateOne ( { _id : course . id } , { $set : updates } ) ;
294- console . log ( ` ✓ Course: ${ course . title } ` ) ;
385+ const result = await CourseModel . updateOne (
386+ { _id : ( course as any ) . _id } ,
387+ { $set : updates } ,
388+ ) ;
389+ if ( result . matchedCount === 0 ) {
390+ console . error (
391+ ` ✗ Failed to update course: ${ course . title } (No document found)` ,
392+ ) ;
393+ } else {
394+ console . log ( ` ✓ Course: ${ course . title } ` ) ;
395+ }
295396 }
296397 }
297398}
@@ -331,8 +432,17 @@ async function refreshLessonMedia(domainId: mongoose.Types.ObjectId) {
331432 }
332433
333434 if ( hasUpdates ) {
334- await LessonModel . updateOne ( { _id : lesson . id } , { $set : updates } ) ;
335- console . log ( ` ✓ Lesson: ${ lesson . title } ` ) ;
435+ const result = await LessonModel . updateOne (
436+ { _id : ( lesson as any ) . _id } ,
437+ { $set : updates } ,
438+ ) ;
439+ if ( result . matchedCount === 0 ) {
440+ console . error (
441+ ` ✗ Failed to update lesson: ${ lesson . title } (No document found)` ,
442+ ) ;
443+ } else {
444+ console . log ( ` ✓ Lesson: ${ lesson . title } ` ) ;
445+ }
336446 }
337447 }
338448}
@@ -353,7 +463,7 @@ async function refreshUserMedia(domainId: mongoose.Types.ObjectId) {
353463 const updatedMedia = await refreshMediaObject ( user . avatar ) ;
354464 if ( updatedMedia ) {
355465 await UserModel . updateOne (
356- { _id : user . _id } ,
466+ { _id : ( user as any ) . _id } ,
357467 { $set : { avatar : updatedMedia } } ,
358468 ) ;
359469 console . log ( ` ✓ User: ${ user . email } ` ) ;
@@ -700,8 +810,11 @@ async function main() {
700810
701811 if ( discoverMode ) {
702812 console . log (
703- "🔍 DISCOVER MODE: Only printing media objects, no updates\n " ,
813+ "🔍 DRY RUN (Discover Mode): Showing changes without updating the database. " ,
704814 ) ;
815+ console . log ( " To apply changes, run with --save\n" ) ;
816+ } else {
817+ console . log ( "💾 SAVE MODE: Updating database with fresh URLs\n" ) ;
705818 }
706819
707820 if ( domainArg ) {
0 commit comments