Skip to main content
These are internal Convex actions. They are called automatically by the system and are not accessible from client code. They are documented here for observability and debugging purposes.
All media storage actions run in Node.js and communicate with a NextCloud instance over WebDAV. They require the following environment variables:
  • NEXTCLOUD_WEBDAV_BASE_URL — WebDAV base URL (e.g. https://cloud.example.com/remote.php/dav/files/user)
  • NEXTCLOUD_WEBDAV_USER — NextCloud username
  • NEXTCLOUD_WEBDAV_APP_PASSWORD — NextCloud app password
  • NEXTCLOUD_UPLOAD_PREFIX — Storage path prefix (default: pindeck/media-uploads)
Optionally, a MEDIA_GATEWAY_URL / MEDIA_GATEWAY_TOKEN can be configured to route uploads through an external media processing service instead of processing images directly.

Derivative URL structure

Every persisted image produces four variants stored under a dated directory path:
pindeck/media-uploads/{year}/{MM_DD}/
  original/{basename}.{ext}       ← full resolution original
  preview/{basename}-preview.webp ← 640×360 WebP (cover crop)
  low/{basename}-w320.webp        ← 320×180 WebP thumbnail
  high/{basename}-w1280.webp      ← 1280×720 WebP
  high/{basename}-w1920.webp      ← 1920×1080 WebP
The derivativeUrls object returned by storage actions maps these to public URLs:
{
  small: "https://cloud.example.com/.../low/{basename}-w320.webp",
  medium: "https://cloud.example.com/.../high/{basename}-w1280.webp",
  large: "https://cloud.example.com/.../high/{basename}-w1920.webp",
}

NextCloud persistence status flow

The nextcloudPersistStatus field on an image record tracks the upload lifecycle: Images created via uploadMultiple start with nextcloudPersistStatus: "pending". Once finalizeUploadedImage completes successfully, the status transitions to "succeeded" and the temporary Convex storage file is deleted.

Actions

internal.mediaStorage.finalizeUploadedImage

Called automatically after api.images.uploadMultiple. Retrieves the image from temporary Convex storage, uploads it to NextCloud (generating preview and derivative variants), updates the image record with the persisted URLs, deletes the temporary Convex file, and schedules VLM smart analysis.
imageId
Id<'images'>
required
ID of the draft image record to finalize.
userId
Id<'users'>
required
ID of the uploading user, passed through to the VLM analysis job.
storageId
Id<'_storage'>
required
Convex storage ID of the uploaded file.
title
string
required
Image title, used for file naming and VLM context.
tags
string[]
required
Tags passed to the VLM analysis job.
category
string
required
Category passed to the VLM analysis job.
description
string
Description passed to the VLM analysis job.
source
string
Source attribution passed to the VLM analysis job.
sref
string
Style reference string passed to the VLM analysis job.
group
string
Project group passed to the VLM analysis job.
projectName
string
Project name passed to the VLM analysis job.
moodboardName
string
Moodboard name passed to the VLM analysis job.
variationCount
number
Number of variations to auto-generate after analysis. Clamped to 0–12. Defaults to 0.
sourceType
string
One of upload, discord, pinterest, ai.
Returns { ok: true, imageUrl: string } | { ok: false, error: string } On failure, the image record’s nextcloudPersistStatus is set to "failed" and aiStatus is set to "failed".

internal.mediaStorage.persistExternalImageFromUrl

Downloads an image from an external URL, generates a preview and three derivative sizes, and uploads all files to NextCloud. Used during Discord and Pinterest imports.
sourceUrl
string
required
Public URL of the image to download and persist.
title
string
Optional title, used for the NextCloud file name.
Returns
imageUrl
string
required
Public NextCloud URL of the stored original.
previewUrl
string
required
Public URL of the 640×360 WebP preview.
storagePath
string
required
Relative WebDAV path of the original file.
previewStoragePath
string
required
Relative WebDAV path of the preview file.
derivativeUrls
object
required
Public URLs for the three derivative sizes.
derivativeStoragePaths
object
required
WebDAV paths for the three derivative files.

internal.mediaStorage.persistGeneratedImageFromUrl

Same behavior as persistExternalImageFromUrl, but designed for AI-generated image URLs from fal.ai. Returns a discriminated union to allow graceful error handling when NextCloud is unavailable.
sourceUrl
string
required
URL of the AI-generated image to persist.
title
string
Optional title for file naming.
Returns { ok: true, imageUrl, previewUrl, storagePath, previewStoragePath, derivativeUrls, derivativeStoragePaths } | { ok: false, error: string } When NextCloud is not configured (ok: false), error is "Nextcloud not configured". The caller (internalGenerateRelatedImages) treats this as a non-fatal condition and continues.

internal.mediaStorage.publishStoredImagePaths

Derives public NextCloud share URLs from already-stored WebDAV paths. Used by the backfill migration to publish images that were uploaded privately without a public share link.
storagePath
string
required
Relative WebDAV path of the stored original.
previewStoragePath
string
Relative WebDAV path of the preview file.
derivativeStoragePaths
object
Relative WebDAV paths for derivative files.
Returns
imageUrl
string
required
Public URL for the original.
previewUrl
string
Public URL for the preview (if previewStoragePath was provided).
derivativeUrls
object
Public URLs for derivatives (if derivativeStoragePaths was provided).

internal.mediaStorage.reprocessStoredImagePaths

Re-downloads the stored original from NextCloud and regenerates all preview and derivative variants. Used by the backfill migration when derivatives are missing or stale.
storagePath
string
required
Relative WebDAV path of the stored original to reprocess.
title
string
Optional title for file naming of regenerated derivatives.
Returns the full UploadedImage object: { imageUrl, previewUrl, storagePath, previewStoragePath, derivativeUrls, derivativeStoragePaths }.

internal.mediaStorage.cleanupNextcloudPaths

Deletes a list of files from NextCloud. Called automatically when an image is rejected via api.images.rejectImage or deleted via api.images.remove.
paths
string[]
required
Relative WebDAV paths to delete. Duplicates are deduplicated before deletion. Empty or blank strings are skipped.
Returns
deleted
number
required
Number of files successfully deleted.
failed
number
required
Number of files that could not be deleted. Failures are logged as warnings but do not throw.
A failed count greater than 0 usually means the file was already absent from NextCloud. It is safe to ignore in most cases.

Build docs developers (and LLMs) love