UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 16.5 kB
{"version":3,"file":"create-single.mjs","names":["MediaTranslationsRepository","mediaServices.checks.checkMediaKeyAccess","mediaServices.checks.checkAwaitingSync","mediaServices.strategies.syncMedia","mediaServices.strategies.deleteObject","constants","mediaFormatter"],"sources":["../../../src/services/media/create-single.ts"],"sourcesContent":["import constants from \"../../constants/constants.js\";\nimport { mediaFormatter } from \"../../libs/formatters/index.js\";\nimport executeHooks from \"../../libs/hooks/execute-hooks.js\";\nimport { copy } from \"../../libs/i18n/index.js\";\nimport cacheKeys from \"../../libs/kv/cache-keys.js\";\nimport { invalidateHttpCacheTags } from \"../../libs/kv/http-cache.js\";\nimport {\n\tMediaAwaitingSyncRepository,\n\tMediaRepository,\n\tMediaTranslationsRepository,\n} from \"../../libs/repositories/index.js\";\nimport type {\n\tMediaCropInput,\n\tMediaOrigin,\n\tMediaType,\n} from \"../../types/response.js\";\nimport type { Media } from \"../../types.js\";\nimport { getBaseUrl } from \"../../utils/helpers/index.js\";\nimport getKeyVisibility from \"../../utils/media/get-key-visibility.js\";\nimport { resolveMediaKeyTenant } from \"../../utils/media/index.js\";\nimport type { ServiceFn } from \"../../utils/services/types.js\";\nimport { mediaServices } from \"../index.js\";\nimport checkFolderAccess from \"../media-folders/checks/check-folder-access.js\";\nimport checkFolderTenantCompatibility from \"./helpers/check-folder-tenant-compatibility.js\";\nimport prepareMediaTranslations from \"./helpers/prepare-media-translations.js\";\nimport resolveAiGeneration from \"./helpers/resolve-ai-generation.js\";\nimport resolvePoster from \"./helpers/resolve-poster.js\";\nimport syncOwnedVisibility from \"./helpers/sync-owned-visibility.js\";\nimport upsertCrop from \"./helpers/upsert-crop.js\";\n\nconst createSingle: ServiceFn<\n\t[\n\t\t{\n\t\t\tkey: string;\n\t\t\tfileName: string;\n\t\t\twidth?: number;\n\t\t\theight?: number;\n\t\t\tfocalPoint?: {\n\t\t\t\tx: number;\n\t\t\t\ty: number;\n\t\t\t};\n\t\t\tblurHash?: string;\n\t\t\taverageColor?: string;\n\t\t\tbase64?: string | null;\n\t\t\tisDark?: boolean;\n\t\t\tisLight?: boolean;\n\t\t\ttitle?: {\n\t\t\t\tlocaleCode: string;\n\t\t\t\tvalue: string | null;\n\t\t\t}[];\n\t\t\talt?: {\n\t\t\t\tlocaleCode: string;\n\t\t\t\tvalue: string | null;\n\t\t\t}[];\n\t\t\tdescription?: {\n\t\t\t\tlocaleCode: string;\n\t\t\t\tvalue: string | null;\n\t\t\t}[];\n\t\t\tsummary?: {\n\t\t\t\tlocaleCode: string;\n\t\t\t\tvalue: string | null;\n\t\t\t}[];\n\t\t\tfolderId?: number | null;\n\t\t\tisHidden?: boolean;\n\t\t\tposterId?: number | null;\n\t\t\tcrop?: MediaCropInput;\n\t\t\torigin: MediaOrigin;\n\t\t\taiGenerationRequestId?: string;\n\t\t\tallowedType?: MediaType;\n\t\t\tuserId: number;\n\t\t},\n\t],\n\tMedia\n> = async (context, data) => {\n\tconst Media = new MediaRepository(context.db.client, context.config.db);\n\tconst MediaTranslations = new MediaTranslationsRepository(\n\t\tcontext.db.client,\n\t\tcontext.config.db,\n\t);\n\tconst MediaAwaitingSync = new MediaAwaitingSyncRepository(\n\t\tcontext.db.client,\n\t\tcontext.config.db,\n\t);\n\n\tconst folderAccessRes = await checkFolderAccess(context, {\n\t\tfolderId: data.folderId,\n\t});\n\tif (folderAccessRes.error) return folderAccessRes;\n\n\tconst mediaTenantKey = context.request.tenantKey ?? null;\n\n\tconst folderTenantRes = checkFolderTenantCompatibility({\n\t\tfolderId: data.folderId,\n\t\tfolderTenantKey: folderAccessRes.data?.tenant_key ?? null,\n\t\tmediaTenantKey,\n\t});\n\tif (folderTenantRes.error) return folderTenantRes;\n\n\tconst keyAccessRes = await mediaServices.checks.checkMediaKeyAccess(context, {\n\t\tkey: data.key,\n\t});\n\tif (keyAccessRes.error) return keyAccessRes;\n\n\tconst awaitingSyncRes = await mediaServices.checks.checkAwaitingSync(\n\t\tcontext,\n\t\t{\n\t\t\tkey: data.key,\n\t\t},\n\t);\n\tif (awaitingSyncRes.error) return awaitingSyncRes;\n\n\tconst syncMediaRes = await mediaServices.strategies.syncMedia(context, {\n\t\tkey: data.key,\n\t\tfileName: data.fileName,\n\t\tallowedType: data.allowedType,\n\t});\n\tif (syncMediaRes.error) return syncMediaRes;\n\tconst mediaKey = syncMediaRes.data.key;\n\n\tif (data.focalPoint !== undefined && syncMediaRes.data.type !== \"image\") {\n\t\tawait mediaServices.strategies.deleteObject(context, {\n\t\t\tkey: mediaKey,\n\t\t\tsize: syncMediaRes.data.size,\n\t\t\tprocessedSize: 0,\n\t\t\ttenantKey: mediaTenantKey,\n\t\t});\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 400,\n\t\t\t\terrors: {\n\t\t\t\t\tfocalPoint: {\n\t\t\t\t\t\tcode: \"media_error\",\n\t\t\t\t\t\tmessage: copy(\"server:core.media.errors.focal.point.image.only\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\tif (data.crop !== undefined && syncMediaRes.data.type !== \"image\") {\n\t\tawait mediaServices.strategies.deleteObject(context, {\n\t\t\tkey: mediaKey,\n\t\t\tsize: syncMediaRes.data.size,\n\t\t\tprocessedSize: 0,\n\t\t\ttenantKey: mediaTenantKey,\n\t\t});\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 400,\n\t\t\t\tmessage: copy(\"server:core.media.errors.image.only\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst keyVisibility = getKeyVisibility(mediaKey);\n\n\t//* we infer the public value based on the key so there cannot be drift between the media uploaded via the\n\t//* upload endpoint and this media update endpoint which the SPA calls afterwards\n\tconst isPublic = keyVisibility === constants.media.visibilityKeys.public;\n\n\tif (data.posterId != null && syncMediaRes.data.type !== \"video\") {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 400,\n\t\t\t\tmessage: copy(\"server:core.media.poster.video.only\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\t//* verify the poster exists\n\tif (data.posterId !== undefined && data.posterId !== null) {\n\t\tconst posterRes = await resolvePoster(context, {\n\t\t\tposterId: data.posterId,\n\t\t});\n\t\tif (posterRes.error) return posterRes;\n\t}\n\n\tconst aiGenerationRes = await resolveAiGeneration(context, {\n\t\torigin: data.origin,\n\t\taiGenerationRequestId: data.aiGenerationRequestId,\n\t});\n\tif (aiGenerationRes.error) return aiGenerationRes;\n\tconst isImage = syncMediaRes.data.type === \"image\";\n\n\tconst [mediaRes, deleteMediaSyncRes] = await Promise.all([\n\t\tMedia.createSingle({\n\t\t\tdata: {\n\t\t\t\tkey: mediaKey,\n\t\t\t\tparent_media_id: null,\n\t\t\t\trelation_type: null,\n\t\t\t\te_tag: syncMediaRes.data.etag ?? undefined,\n\t\t\t\torigin: data.origin,\n\t\t\t\tai_generation_id: aiGenerationRes.data,\n\t\t\t\tpublic: isPublic,\n\t\t\t\ttype: syncMediaRes.data.type,\n\t\t\t\tmime_type: syncMediaRes.data.mimeType,\n\t\t\t\tfile_extension: syncMediaRes.data.extension,\n\t\t\t\tfile_name: data.fileName,\n\t\t\t\tfile_size: syncMediaRes.data.size,\n\t\t\t\twidth: isImage ? (data.width ?? null) : null,\n\t\t\t\theight: isImage ? (data.height ?? null) : null,\n\t\t\t\tfocal_x:\n\t\t\t\t\tsyncMediaRes.data.type === \"image\" && data.focalPoint\n\t\t\t\t\t\t? Math.round(data.focalPoint.x * 10000)\n\t\t\t\t\t\t: null,\n\t\t\t\tfocal_y:\n\t\t\t\t\tsyncMediaRes.data.type === \"image\" && data.focalPoint\n\t\t\t\t\t\t? Math.round(data.focalPoint.y * 10000)\n\t\t\t\t\t\t: null,\n\t\t\t\tblur_hash: isImage ? (data.blurHash ?? null) : null,\n\t\t\t\taverage_color: isImage ? (data.averageColor ?? null) : null,\n\t\t\t\tbase64: isImage ? (data.base64 ?? null) : null,\n\t\t\t\tis_dark: isImage ? (data.isDark ?? null) : null,\n\t\t\t\tis_light: isImage ? (data.isLight ?? null) : null,\n\t\t\t\tfolder_id: data.folderId ?? null,\n\t\t\t\tis_hidden: data.isHidden ?? false,\n\t\t\t\ttenant_key: mediaTenantKey,\n\t\t\t\tcreated_by: data.userId,\n\t\t\t\tupdated_by: data.userId,\n\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\tcreated_at: new Date().toISOString(),\n\t\t\t},\n\t\t\treturning: [\"id\"],\n\t\t}),\n\t\tMediaAwaitingSync.deleteSingle({\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tkey: \"key\",\n\t\t\t\t\toperator: \"=\",\n\t\t\t\t\tvalue: data.key,\n\t\t\t\t},\n\t\t\t],\n\t\t\treturning: [\"key\"],\n\t\t\tvalidation: {\n\t\t\t\tenabled: true,\n\t\t\t},\n\t\t}),\n\t]);\n\tif (mediaRes.error) return mediaRes;\n\tif (deleteMediaSyncRes.error) return deleteMediaSyncRes;\n\n\tif (mediaRes.data === undefined) {\n\t\tif (context.media) {\n\t\t\tawait context.media.delete(context, {\n\t\t\t\tkey: mediaKey,\n\t\t\t\ttenant: resolveMediaKeyTenant(context.config, mediaKey),\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 500,\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tif (data.posterId !== undefined && data.posterId !== null) {\n\t\tconst hidePosterRes = await Media.updateSingle({\n\t\t\twhere: [{ key: \"id\", operator: \"=\", value: data.posterId }],\n\t\t\tdata: {\n\t\t\t\tis_hidden: true,\n\t\t\t\tfolder_id: null,\n\t\t\t\tparent_media_id: mediaRes.data.id,\n\t\t\t\trelation_type: \"poster\",\n\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\tupdated_by: data.userId,\n\t\t\t},\n\t\t\tvalidation: {\n\t\t\t\tenabled: true,\n\t\t\t},\n\t\t});\n\t\tif (hidePosterRes.error) return hidePosterRes;\n\t}\n\n\tif (data.crop) {\n\t\tconst cropRes = await upsertCrop(context, {\n\t\t\tparent: {\n\t\t\t\tid: mediaRes.data.id,\n\t\t\t\tkey: mediaKey,\n\t\t\t\ttype: syncMediaRes.data.type,\n\t\t\t\torigin: data.origin,\n\t\t\t\tpublic: isPublic,\n\t\t\t\ttenant_key: mediaTenantKey,\n\t\t\t\trelation_type: null,\n\t\t\t},\n\t\t\tcrop: data.crop,\n\t\t\tuserId: data.userId,\n\t\t});\n\t\tif (cropRes.error) return cropRes;\n\t}\n\n\tconst visibilityRes = await syncOwnedVisibility(context, {\n\t\tparentId: mediaRes.data.id,\n\t\tpublic: isPublic,\n\t\tuserId: data.userId,\n\t});\n\tif (visibilityRes.error) return visibilityRes;\n\n\tconst translations = prepareMediaTranslations({\n\t\ttitle: data.title || [],\n\t\talt: isImage ? (data.alt ?? []) : [],\n\t\tdescription:\n\t\t\tsyncMediaRes.data.type === \"video\" || syncMediaRes.data.type === \"audio\"\n\t\t\t\t? (data.description ?? [])\n\t\t\t\t: [],\n\t\tsummary: syncMediaRes.data.type === \"document\" ? (data.summary ?? []) : [],\n\t\tmediaId: mediaRes.data.id,\n\t});\n\tif (translations.length > 0) {\n\t\tconst mediaTranslationsRes = await MediaTranslations.upsertMultiple({\n\t\t\tdata: translations,\n\t\t\treturning: [\"id\"],\n\t\t\tvalidation: {\n\t\t\t\tenabled: true,\n\t\t\t},\n\t\t});\n\t\tif (mediaTranslationsRes.error) {\n\t\t\tif (context.media) {\n\t\t\t\tawait context.media.delete(context, {\n\t\t\t\t\tkey: mediaKey,\n\t\t\t\t\ttenant: resolveMediaKeyTenant(context.config, mediaKey),\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn mediaTranslationsRes;\n\t\t}\n\t}\n\n\tawait invalidateHttpCacheTags(context, [cacheKeys.http.tags.clientMedia]);\n\n\tconst mediaFetchRes = await Media.selectSingleById({\n\t\tid: mediaRes.data.id,\n\t\ttenantKey: context.request.tenantKey,\n\t\tvalidation: {\n\t\t\tenabled: true,\n\t\t\tdefaultError: {\n\t\t\t\tmessage: copy(\"server:core.media.not.found.message\"),\n\t\t\t\tstatus: 404,\n\t\t\t},\n\t\t},\n\t});\n\tif (mediaFetchRes.error) return mediaFetchRes;\n\n\tconst media = mediaFormatter.formatSingle({\n\t\tmedia: mediaFetchRes.data,\n\t\thost: getBaseUrl(context),\n\t});\n\n\tconst hookRes = await executeHooks(\n\t\tcontext,\n\t\t{\n\t\t\tservice: \"media\",\n\t\t\tevent: \"afterCreate\",\n\t\t\tconfig: context.config,\n\t\t},\n\t\t{\n\t\t\tmeta: {\n\t\t\t\ttenantKey: context.request.tenantKey ?? null,\n\t\t\t},\n\t\t\tdata: {\n\t\t\t\tid: mediaFetchRes.data.id,\n\t\t\t\tuserId: data.userId,\n\t\t\t\tmedia,\n\t\t\t},\n\t\t},\n\t);\n\tif (hookRes.error) return hookRes;\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: media,\n\t};\n};\n\nexport default createSingle;\n"],"mappings":"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"}