Appearance
Admin Upload & External Upload
Overview
This document covers two distinct upload systems in the Admin Frontend application:
Admin Upload: Comprehensive file upload system for authenticated workspace users, supporting direct browser uploads, chunked/multipart uploads for large files, upload queue management, progress tracking, error handling, and retry logic.
External Upload Integration: Guest user upload system with OTP authentication, allowing external users to upload files to workspaces with limited access and controlled permissions.
Both systems handle file validation, progress tracking, error handling, and automatic thumbnail generation.
Architecture
Admin Upload Methods
- Direct Upload: Small files uploaded directly via FormData
- Chunked/Multipart Upload: Large files split into chunks and uploaded in parts to S3
- Bulk Upload: Multiple files uploaded simultaneously with queue management
- Version Upload: Upload new versions of existing assets
External Upload Methods
- External Upload: Guest user uploads with OTP authentication
- Chunked External Upload: Large files for guest users using chunked upload
File Structure
Admin Upload Files
JavaScript Files (.js)
mixins/upload-common.js- Common upload functionality mixin- File validation
- Upload initialization
- Progress tracking helpers
- Error handling utilities
mixins/uploadQueue.js- Upload queue management- Queue operations (add, remove, pause, resume)
- Concurrent upload limits
- Queue state management
- Retry logic
api/s3.js- S3 upload API integration- Multipart upload initiation
- Presigned URL generation
- Upload completion
- Direct upload endpoints
api/index.js- General API (may contain upload endpoints)- Asset creation endpoints
- Upload status endpoints
utils/index.js- Utility functions (may contain upload helpers)- File type detection
- File size formatting
- Upload helpers
store/dam.js- DAM store (upload state management)- Upload queue state
- Upload progress state
- Upload history
Vue Component Files (.vue)
components/dam/AssetUpload.vue- Main asset upload component- File selection interface
- Drag and drop support
- Upload queue display
- Progress indicators
components/dam/upload-chunk.vue- Chunked upload component- File chunking logic
- Chunk upload coordination
- Progress tracking per chunk
- Error recovery
components/dam/Uploaded/UploadedListItem.vue- Uploaded item list component- Display uploaded files
- Upload status indicators
- Action buttons (retry, cancel, remove)
components/dam/Dialogs/miniUploadBackdrop.vue- Mini upload backdrop dialog- Modal backdrop for upload dialog
- Overlay styling
components/dam/Dialogs/miniUploadDialog.vue- Mini upload dialog- Compact upload interface
- Quick file selection
- Minimal progress display
components/dam/Dialogs/VersionUploadBackdrop.vue- Version upload backdrop- Modal for version uploads
- Version-specific upload UI
components/dam/SkeletonLoaders/UploadedTableListSkeleton.vue- Upload skeleton loader- Loading state for upload list
- Skeleton UI during data fetch
pages/_workspace_id/dam/upload.vue- Upload page- Main upload interface
- File selection
- Upload queue management
- Upload history
pages/_workspace_id/dam/uploaded.vue- Uploaded assets page- List of uploaded assets
- Upload status tracking
- Asset management
SVG Icon Components (Upload-related)
components/svg/CollageUploadIcon.vue- Standard upload iconcomponents/svg/CollageUploadMediumIcon.vue- Medium size upload iconcomponents/svg/CollageUploadLargeIcon.vue- Large size upload iconcomponents/svg/CollageUploadExtraLargeIcon.vue- Extra large upload iconcomponents/svg/CollageUploadCloudIcon.vue- Cloud upload icon
External Upload Files
JavaScript Files (.js)
mixins/external-uploads.js- External upload functionality mixin- Guest user upload handling
- OTP verification integration
- External upload queue management
- Guest-specific validation
middleware/external-upload-auth.js- External upload authentication middleware- Guest user authentication
- Token validation
- Access control
middleware/external-guest-redirect.js- External guest redirect middleware- Redirect logic for guest users
- Access verification
- Route protection
middleware/external-otp-verify.js- External OTP verification middleware- OTP code verification
- Session management
- Access grant
Vue Component Files (.vue)
components/dam/upload-chunk-external.vue- External chunked upload component- Guest user chunked uploads
- OTP-verified upload flow
- External upload progress
pages/_workspace_id/external/upload.vue- External upload page- Guest upload interface
- OTP input
- External upload queue
pages/_workspace_id/external/verify.vue- External verification page- OTP verification interface
- Access request handling
- Verification status
pages/_workspace_id/external/request-access.vue- External request access page- Access request form
- Email/contact input
- Request submission
pages/_workspace_id/external/request-submitted.vue- External request submitted page- Confirmation page
- Next steps information
- Status display
Additional Files
components/dam/AssetList/Versions.vue- May handle version uploads- Version upload interface
- Version management
- Version history
Admin Upload System
Upload Common Mixin
File: mixins/upload-common.js
This mixin provides common upload functionality shared across upload components.
Key Methods
javascript
// File validation
validateFile(file) {
// Check file size
// Check file type
// Return validation result
}
// Initialize upload
initUpload(file, options) {
// Prepare file for upload
// Set upload metadata
// Return upload object
}
// Track progress
updateProgress(uploadId, progress) {
// Update upload progress
// Emit progress events
}
// Handle errors
handleUploadError(uploadId, error) {
// Log error
// Update upload status
// Trigger retry if applicable
}Usage
vue
<script>
import uploadCommon from '~/mixins/upload-common'
export default {
mixins: [uploadCommon],
methods: {
async handleFileSelect(files) {
for (const file of files) {
const validation = this.validateFile(file)
if (validation.valid) {
const upload = this.initUpload(file, {
workspaceId: this.$route.params.workspace_id,
folderId: this.currentFolderId
})
this.addToQueue(upload)
} else {
this.showError(validation.message)
}
}
}
}
}
</script>Upload Queue Mixin
File: mixins/uploadQueue.js
Manages the upload queue with concurrent upload limits, retry logic, and state management.
Key Methods
javascript
// Add file to queue
addToQueue(upload) {
// Add to queue array
// Trigger upload if under limit
}
// Remove from queue
removeFromQueue(uploadId) {
// Remove from queue
// Cancel active upload if in progress
}
// Pause upload
pauseUpload(uploadId) {
// Pause upload
// Save current state
}
// Resume upload
resumeUpload(uploadId) {
// Resume from saved state
// Continue upload
}
// Process queue
processQueue() {
// Check concurrent limit
// Start next uploads
// Manage queue state
}Queue State
javascript
data() {
return {
uploadQueue: [], // Array of upload objects
activeUploads: [], // Currently uploading files
maxConcurrent: 3, // Max concurrent uploads
pausedUploads: [], // Paused uploads
failedUploads: [] // Failed uploads (for retry)
}
}Usage
vue
<script>
import uploadQueue from '~/mixins/uploadQueue'
export default {
mixins: [uploadQueue],
methods: {
handleFiles(files) {
files.forEach(file => {
const upload = {
id: this.generateId(),
file: file,
status: 'pending',
progress: 0,
error: null
}
this.addToQueue(upload)
})
}
}
}
</script>Asset Upload Component
File: components/dam/AssetUpload.vue
Main upload component providing file selection, drag-and-drop, and upload queue display.
Features
- Drag and drop file selection
- Multiple file selection
- File validation
- Upload queue display
- Progress tracking
- Error handling
- Retry functionality
Props
javascript
{
workspaceId: {
type: String,
required: true
},
folderId: {
type: String,
default: null
},
maxFileSize: {
type: Number,
default: 1073741824 // 1GB
},
allowedTypes: {
type: Array,
default: () => []
},
maxConcurrent: {
type: Number,
default: 3
}
}Events
javascript
{
'upload-complete': (asset) => {}, // Upload completed
'upload-error': (error) => {}, // Upload error
'upload-progress': (progress) => {}, // Progress update
'queue-updated': (queue) => {} // Queue state changed
}Usage
vue
<template>
<AssetUpload
:workspace-id="workspaceId"
:folder-id="currentFolderId"
:max-file-size="maxFileSize"
:allowed-types="allowedFileTypes"
@upload-complete="handleUploadComplete"
@upload-error="handleUploadError"
/>
</template>Chunked Upload Component
File: components/dam/upload-chunk.vue
Handles chunked/multipart uploads for large files.
Chunking Logic
javascript
// Split file into chunks
createChunks(file, chunkSize = 5 * 1024 * 1024) {
const chunks = []
let offset = 0
while (offset < file.size) {
const chunk = file.slice(offset, offset + chunkSize)
chunks.push({
data: chunk,
partNumber: chunks.length + 1,
offset: offset,
size: chunk.size
})
offset += chunkSize
}
return chunks
}Upload Flow
javascript
async uploadChunked(file) {
// 1. Start multipart upload
const { uploadId, assetId } = await this.$axios.get('/s3/start-upload', {
params: {
workspaceId: this.workspaceId,
fileType: file.type
}
})
// 2. Create chunks
const chunks = this.createChunks(file)
const parts = []
// 3. Upload each chunk
for (const chunk of chunks) {
// Get presigned URL
const { url } = await this.$axios.get('/s3/get-upload-url', {
params: {
PartNumber: chunk.partNumber,
UploadId: uploadId,
assetId: assetId,
workspaceId: this.workspaceId
}
})
// Upload chunk to S3
const etag = await this.uploadChunkToS3(url, chunk.data)
parts.push({
ETag: etag,
PartNumber: chunk.partNumber
})
// Update progress
this.updateProgress((chunk.partNumber / chunks.length) * 100)
}
// 4. Complete upload
await this.$axios.post('/s3/complete-upload', {
uploadId,
assetId,
workspaceId: this.workspaceId,
parts
})
}Upload Pages
Upload Page
File: pages/_workspace_id/dam/upload.vue
Main upload interface page.
Route: /:workspace_id/dam/upload
Features:
- File selection interface
- Upload queue management
- Progress monitoring
- Upload history
- Error handling
Uploaded Assets Page
File: pages/_workspace_id/dam/uploaded.vue
Displays list of uploaded assets.
Route: /:workspace_id/dam/uploaded
Features:
- List of uploaded assets
- Upload status tracking
- Asset actions (view, download, delete)
- Filtering and sorting
External Upload System
External Uploads Mixin
File: mixins/external-uploads.js
Handles external/guest user uploads with OTP authentication.
Key Methods
javascript
// Verify OTP
async verifyOTP(otpCode) {
// Verify OTP with backend
// Store verification token
// Grant upload access
}
// External upload
async externalUpload(file, otpToken) {
// Validate guest access
// Initialize external upload
// Use external upload queue
}
// Request access
async requestAccess(email, message) {
// Submit access request
// Send notification
// Return request ID
}External Upload Middleware
External Upload Auth
File: middleware/external-upload-auth.js
Validates guest user authentication for external uploads.
javascript
export default function ({ route, store, redirect }) {
// Check for guest token
// Validate token
// Redirect if invalid
// Allow access if valid
}External Guest Redirect
File: middleware/external-guest-redirect.js
Handles redirects for guest users.
javascript
export default function ({ route, store, redirect }) {
// Check if guest user
// Redirect to appropriate page
// Handle access requests
}External OTP Verify
File: middleware/external-otp-verify.js
Verifies OTP codes for external uploads.
javascript
export default async function ({ route, store, redirect, $axios }) {
// Get OTP from route/query
// Verify OTP with backend
// Store verification status
// Redirect based on result
}External Upload Component
File: components/dam/upload-chunk-external.vue
Chunked upload component specifically for external/guest users.
Features:
- OTP-verified uploads
- Guest user file handling
- External upload queue
- Limited access controls
External Upload Pages
External Upload Page
File: pages/_workspace_id/external/upload.vue
Route: /:workspace_id/external/upload
Features:
- Guest upload interface
- OTP input/verification
- External upload queue
- Limited functionality
External Verify Page
File: pages/_workspace_id/external/verify.vue
Route: /:workspace_id/external/verify
Features:
- OTP verification interface
- Access request handling
- Verification status display
External Request Access Page
File: pages/_workspace_id/external/request-access.vue
Route: /:workspace_id/external/request-access
Features:
- Access request form
- Email/contact input
- Request submission
- Validation
External Request Submitted Page
File: pages/_workspace_id/external/request-submitted.vue
Route: /:workspace_id/external/request-submitted
Features:
- Confirmation message
- Next steps information
- Status display
Upload Queue System
Queue Management
The upload queue system manages multiple file uploads with the following features:
- Concurrent Upload Limits: Maximum number of simultaneous uploads
- Queue Processing: Automatic queue processing when slots available
- Priority Management: Priority-based queue ordering
- State Persistence: Queue state saved across page reloads
Queue States
javascript
{
pending: 'pending', // Waiting in queue
uploading: 'uploading', // Currently uploading
paused: 'paused', // Paused by user
completed: 'completed', // Upload completed
error: 'error', // Upload failed
cancelled: 'cancelled' // Cancelled by user
}Progress Tracking
javascript
{
uploadId: 'unique-id',
fileName: 'example.jpg',
fileSize: 5242880,
uploaded: 2621440,
percentage: 50,
speed: 1024000, // bytes per second
timeRemaining: 2.5, // seconds
status: 'uploading'
}Error Handling and Retry
javascript
// Automatic retry logic
async retryUpload(uploadId) {
const upload = this.getUpload(uploadId)
// Reset upload state
upload.status = 'pending'
upload.error = null
upload.retryCount = (upload.retryCount || 0) + 1
// Check retry limit
if (upload.retryCount <= this.maxRetries) {
// Re-add to queue
this.addToQueue(upload)
} else {
// Mark as failed
upload.status = 'error'
this.handleFinalFailure(upload)
}
}Chunked Upload Implementation
Chunk Creation
Files are split into chunks based on size threshold (typically 5MB per chunk).
javascript
const CHUNK_SIZE = 5 * 1024 * 1024 // 5MB
function createChunks(file) {
const chunks = []
let start = 0
while (start < file.size) {
const end = Math.min(start + CHUNK_SIZE, file.size)
chunks.push({
blob: file.slice(start, end),
partNumber: chunks.length + 1,
start: start,
end: end,
size: end - start
})
start = end
}
return chunks
}Multipart Upload Flow
1. Start Multipart Upload
↓
GET /s3/start-upload
Returns: { uploadId, assetId }
↓
2. For Each Chunk:
↓
a. Get Presigned URL
GET /s3/get-upload-url
Returns: { url, expiresIn }
↓
b. Upload Chunk to S3
PUT {url} (direct to S3)
Returns: ETag header
↓
c. Store Part Info
{ ETag, PartNumber }
↓
3. Complete Multipart Upload
↓
POST /s3/complete-upload
Body: { uploadId, assetId, parts: [{ETag, PartNumber}] }
↓
4. Asset Created
↓
Backend creates asset record
Generates thumbnail (async)
Indexes in Typesense (async)Progress Tracking Per Chunk
javascript
// Track progress for each chunk
updateChunkProgress(uploadId, chunkIndex, progress) {
const upload = this.getUpload(uploadId)
const totalChunks = upload.chunks.length
const chunkProgress = progress / totalChunks
const completedChunks = upload.completedChunks || 0
upload.progress = (completedChunks / totalChunks) * 100 + chunkProgress
this.emit('progress', {
uploadId,
progress: upload.progress,
chunkIndex,
chunkProgress
})
}Error Recovery
javascript
// Handle chunk upload failure
async handleChunkError(uploadId, chunkIndex, error) {
const upload = this.getUpload(uploadId)
const chunk = upload.chunks[chunkIndex]
// Retry chunk upload
if (chunk.retryCount < this.maxChunkRetries) {
chunk.retryCount++
await this.uploadChunk(uploadId, chunkIndex)
} else {
// Abort multipart upload
await this.abortMultipartUpload(upload.uploadId)
upload.status = 'error'
upload.error = error
}
}API Integration
S3 Upload API
File: api/s3.js
See S3 API Documentation for complete API reference.
Key Endpoints
GET /s3/start-upload- Initiate multipart uploadGET /s3/get-upload-url- Get presigned URL for chunkPOST /s3/complete-upload- Complete multipart uploadPOST /api/assets/upload- Direct upload endpoint
Upload Endpoints
Direct Upload
javascript
POST /api/assets/upload
Content-Type: multipart/form-data
FormData:
- file: File object
- workspace_id: string
- folder_id: string (optional)
- metadata: object (optional)Multipart Upload
See AWS S3 Integration for complete multipart upload flow.
Workflows
Admin Upload Workflow
1. User navigates to upload page
Route: /:workspace_id/dam/upload
Component: pages/_workspace_id/dam/upload.vue
↓
2. User selects file(s)
Component: AssetUpload.vue
- Drag and drop OR file input
- Multiple file selection supported
↓
3. File validation
Mixin: upload-common.js
- Check file size (maxFileSize limit)
- Check file type (allowedTypes)
- Validate permissions (workspace/folder)
- Show error if validation fails
↓
4. Initialize upload objects
Mixin: upload-common.js
- Create upload object for each file
- Set metadata (workspaceId, folderId, etc.)
- Generate unique upload ID
↓
5. Add to upload queue
Mixin: uploadQueue.js
- Add upload to queue array
- Set status: 'pending'
- Display in UploadedListItem component
↓
6. Queue processing
Mixin: uploadQueue.js
- Check concurrent upload limit (maxConcurrent)
- If slot available → Start upload
- If queue full → Wait for slot
↓
7. Upload decision
Component: upload-chunk.vue (for large files)
- If file < threshold (e.g., 10MB) → Direct upload
POST /api/assets/upload (FormData)
- If file >= threshold → Chunked upload
Use multipart upload flow
↓
8. Upload execution
For Direct Upload:
- POST /api/assets/upload
- Track progress via onUploadProgress
- Update progress in queue
↓
For Chunked Upload:
- Start multipart upload
- Upload chunks sequentially/parallel
- Track progress per chunk
- Handle chunk errors
↓
9. Error handling
- If error occurs → Retry logic
- Update status: 'error'
- Show error message
- Allow manual retry
↓
10. Upload completion
- Backend creates asset record
- Generate thumbnail (async background job)
- Index in Typesense (async background job)
- Return asset data to frontend
↓
11. Update UI
Component: UploadedListItem.vue
- Update status: 'completed'
- Remove from active queue
- Show success message/notification
- Emit 'upload-complete' event
↓
12. Refresh asset list
- Update DAM store
- Refresh asset list if on uploaded page
- Navigate to asset details (optional)Version Upload Workflow
1. User navigates to asset details
Route: /:workspace_id/dam/assets/:asset_id
↓
2. User clicks "Upload New Version"
Component: Versions.vue
Dialog: VersionUploadBackdrop.vue
↓
3. File selection
- Select new version file
- Validate file (same or compatible type)
↓
4. Version upload
- Use same upload flow as admin upload
- Associate with existing asset
- Version number auto-incremented
↓
5. Version creation
- Create version record
- Link to parent asset
- Store version metadata
↓
6. Update asset
- Set new version as current (optional)
- Update asset preview
- Show version historyExternal Upload Workflow
1. Guest user accesses external upload page
Route: /:workspace_id/external/upload
↓
2. Access verification
Middleware: external-upload-auth.js
- Check if access granted
- If no access → Redirect to request-access
↓
3. OTP verification (if required)
Route: /:workspace_id/external/verify
Middleware: external-otp-verify.js
- Enter OTP code
- Verify with backend
- Store verification token in session
↓
4. File selection
Component: upload-chunk-external.vue
- Select file(s)
- Validate file (size, type)
- Use external-uploads.js mixin
↓
5. Add to external upload queue
- Limited concurrent uploads (guest restrictions)
- Queue management via external-uploads mixin
↓
6. Upload execution
- Use external upload queue
- Chunked upload for large files
- Track progress per chunk
- Handle errors with retry
↓
7. Upload completion
- Create asset record
- Notify workspace owner (email/notification)
- Show confirmation message
- Redirect to success pageExternal Access Request Workflow
1. Guest user requests access
Route: /:workspace_id/external/request-access
↓
2. Fill access request form
- Enter email/contact information
- Optional message
- Submit request
↓
3. Backend processes request
- Store request in database
- Send notification to workspace owner
- Generate OTP (if auto-approve)
↓
4. Redirect to request-submitted page
Route: /:workspace_id/external/request-submitted
- Show confirmation message
- Display next steps
- Provide OTP (if applicable)
↓
5. Workspace owner approves (manual)
- Owner receives notification
- Owner approves/rejects request
- OTP sent to guest user
↓
6. Guest user verifies OTP
Route: /:workspace_id/external/verify
- Enter OTP code
- Verify and grant access
- Redirect to upload pageChunked Upload Workflow
1. File selected (large file >= threshold)
Component: upload-chunk.vue
Mixin: upload-common.js
↓
2. Start multipart upload
API: GET /s3/start-upload
Params: { workspaceId, fileType }
Response: { uploadId, assetId }
Store: uploadId and assetId in upload object
↓
3. Create chunks
Component: upload-chunk.vue
- Calculate chunk size (default: 5MB)
- Split file using File.slice()
- Create chunk objects:
{
blob: File slice,
partNumber: 1, 2, 3...,
start: byte offset,
end: byte offset,
size: chunk size
}
- Store chunks array in upload object
↓
4. For each chunk (sequential or parallel):
↓
a. Get presigned URL
API: GET /s3/get-upload-url
Params: {
PartNumber: chunk.partNumber,
UploadId: uploadId,
assetId: assetId,
workspaceId: workspaceId
}
Response: { url, expiresIn }
↓
b. Upload chunk to S3
Method: PUT (direct to S3, not through backend)
URL: presigned URL from step a
Body: chunk blob
Headers: Content-Type from file
Response: ETag in response header
↓
c. Store part info
parts.push({
ETag: etag,
PartNumber: chunk.partNumber
})
Update: completedChunks count
↓
d. Update progress
Calculate: (completedChunks / totalChunks) * 100
Emit: progress event
Update: UploadedListItem component
↓
5. Handle chunk errors
- If chunk upload fails:
- Retry chunk (up to maxChunkRetries)
- If retries exhausted → Abort multipart upload
- Update status: 'error'
↓
6. Complete multipart upload
API: POST /s3/complete-upload
Body: {
uploadId: uploadId,
assetId: assetId,
workspaceId: workspaceId,
parts: [
{ ETag: '...', PartNumber: 1 },
{ ETag: '...', PartNumber: 2 },
...
]
}
Response: { success: true, location, key }
↓
7. Backend processing
- Merge all parts in S3
- Create asset record in database
- Store S3 key and metadata
- Trigger background jobs:
* Generate thumbnail (async)
* Index in Typesense (async)
* Extract metadata (async)
↓
8. Upload completion
- Backend returns asset data
- Frontend updates status: 'completed'
- Remove from upload queue
- Emit 'upload-complete' event
- Show success notification
↓
9. UI update
- Refresh asset list
- Navigate to asset details (optional)
- Update DAM storeComponent Integration
Using Upload Components
vue
<template>
<div>
<!-- Main upload component -->
<AssetUpload
:workspace-id="workspaceId"
:folder-id="currentFolderId"
@upload-complete="handleUploadComplete"
/>
<!-- Upload queue display -->
<UploadedListItem
v-for="upload in uploadQueue"
:key="upload.id"
:upload="upload"
@retry="retryUpload"
@cancel="cancelUpload"
/>
</div>
</template>
<script>
import AssetUpload from '~/components/dam/AssetUpload.vue'
import UploadedListItem from '~/components/dam/Uploaded/UploadedListItem.vue'
import uploadQueue from '~/mixins/uploadQueue'
export default {
components: {
AssetUpload,
UploadedListItem
},
mixins: [uploadQueue],
data() {
return {
workspaceId: this.$route.params.workspace_id,
currentFolderId: null
}
},
methods: {
handleUploadComplete(asset) {
// Handle completed upload
this.$store.dispatch('dam/addAsset', asset)
this.$router.push(`/${this.workspaceId}/dam/assets/${asset.id}`)
}
}
}
</script>Related Documentation
- AWS S3 Integration - Storage backend and multipart uploads
- S3 API - S3 upload endpoints
- Mixins - Upload Common - Upload common mixin
- Mixins - Upload Queue - Upload queue mixin
- Mixins - External Uploads - External uploads mixin