@kodeme-io/next-core-storage
v0.8.4
Published
S3-compatible storage client for Next.js + Odoo applications
Downloads
12
Maintainers
Readme
@next-core/storage
S3-compatible storage client and photo compression utilities for Next.js + Odoo applications.
Features
- ✅ S3 Upload - Upload files to any S3-compatible storage
- ✅ Photo Compression - Reduce image file sizes for mobile apps
- ✅ Presigned URLs - Generate temporary access URLs
- ✅ TypeScript - Full type safety
- ✅ Flexible - Works with AWS S3, MinIO, Hetzner, etc.
Installation
pnpm add @next-core/storage @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presignerEnvironment Variables
Add to your .env.local:
S3_ENDPOINT_URL=https://your-s3-endpoint.com
S3_ACCESS_KEY=your_access_key
S3_SECRET_KEY=your_secret_key
S3_BUCKET_NAME=your-bucket-name
S3_REGION=us-east-1Quick Start
1. Create S3 Client
import { createS3Client } from '@next-core/storage'
const s3 = createS3Client({
endpoint: process.env.S3_ENDPOINT_URL!,
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
bucket: process.env.S3_BUCKET_NAME!,
region: 'us-east-1',
})2. Upload Files
import { uploadToS3, uploadVisitPhoto } from '@next-core/storage'
// Upload generic file
const result = await uploadToS3(
s3,
'my-bucket',
fileBuffer,
'document.pdf',
'application/pdf',
'documents' // folder
)
// Upload visit photo (organized by visit ID)
const photo = await uploadVisitPhoto(
s3,
'my-bucket',
base64Image,
123, // visitId
'checkin',
process.env.S3_ENDPOINT_URL
)
console.log(photo.url) // https://endpoint.com/bucket/visits/123/photos/visit_123_checkin_2025-10-06.jpg3. Compress Photos
import { compressPhoto } from '@next-core/storage'
// Compress image to max 500KB
const compressed = await compressPhoto(
base64Image,
500, // maxSizeKB
1920, // maxWidth
1080 // maxHeight
)
// File size reduced from 3MB to 450KB!API Reference
S3 Client Functions
createS3Client(config)
Create S3 client instance.
const client = createS3Client({
endpoint: 'https://s3.amazonaws.com',
accessKeyId: 'YOUR_KEY',
secretAccessKey: 'YOUR_SECRET',
bucket: 'my-bucket',
region: 'us-east-1',
forcePathStyle: true, // Required for some S3-compatible services
})uploadToS3(client, bucket, fileBuffer, fileName, contentType, folder?, endpoint?)
Upload a file to S3.
const { key, url } = await uploadToS3(
client,
'my-bucket',
fileBuffer,
'photo.jpg',
'image/jpeg',
'photos',
'https://endpoint.com'
)Parameters:
client- S3 client instancebucket- Bucket namefileBuffer- Buffer or base64 stringfileName- File namecontentType- MIME typefolder- Optional folder path (default: 'attachments')endpoint- Optional endpoint for constructing URL
Returns: { key: string, url: string }
uploadVisitPhoto(client, bucket, base64Data, visitId, photoType, endpoint?)
Upload a visit photo with organized folder structure.
const { key, url, fileName } = await uploadVisitPhoto(
client,
'my-bucket',
'data:image/jpeg;base64,...',
123,
'checkin',
'https://endpoint.com'
)
// Uploads to: visits/123/photos/visit_123_checkin_2025-10-06.jpguploadAttachment(client, bucket, fileBuffer, fileName, contentType, model, recordId, endpoint?)
Upload an attachment organized by Odoo model.
const { key, url } = await uploadAttachment(
client,
'my-bucket',
buffer,
'invoice.pdf',
'application/pdf',
'account.move',
456,
'https://endpoint.com'
)
// Uploads to: attachments/account.move/456/invoice.pdfgetPresignedUrl(client, bucket, key, expiresIn?)
Generate temporary access URL for private files.
const url = await getPresignedUrl(
client,
'my-bucket',
'private/document.pdf',
3600 // expires in 1 hour
)deleteFromS3(client, bucket, key)
Delete a file from S3.
await deleteFromS3(client, 'my-bucket', 'photos/old-photo.jpg')validateS3Config(config)
Validate S3 configuration.
const isValid = validateS3Config({
endpoint: process.env.S3_ENDPOINT_URL,
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
bucket: process.env.S3_BUCKET_NAME,
})Photo Compression Functions
compressPhoto(base64, maxSizeKB?, maxWidth?, maxHeight?)
Compress a single photo.
const compressed = await compressPhoto(
'data:image/jpeg;base64,...',
500, // max 500KB
1920, // max 1920px width
1080 // max 1080px height
)compressPhotoBatch(base64Images, maxSizeKB?)
Compress multiple photos in parallel.
const compressed = await compressPhotoBatch(
[image1, image2, image3],
300 // max 300KB each
)fileToBase64(file)
Convert File object to base64.
const base64 = await fileToBase64(fileInput.files[0])base64ToBlob(base64, contentType?)
Convert base64 to Blob.
const blob = base64ToBlob('data:image/jpeg;base64,...', 'image/jpeg')blobToBase64(blob)
Convert Blob to base64.
const base64 = await blobToBase64(blob)checkStorageQuota()
Check browser storage quota.
const { usedMB, availableMB, percentUsed } = await checkStorageQuota()
console.log(`Using ${usedMB.toFixed(2)}MB of ${availableMB.toFixed(2)}MB (${percentUsed.toFixed(1)}%)`)Common Use Cases
Upload Photo from Camera
import { compressPhoto, uploadVisitPhoto, createS3Client } from '@next-core/storage'
const s3 = createS3Client({...})
async function handlePhotoCapture(base64Image: string, visitId: number) {
// 1. Compress photo (3MB → 400KB)
const compressed = await compressPhoto(base64Image, 500)
// 2. Upload to S3
const result = await uploadVisitPhoto(
s3,
process.env.S3_BUCKET_NAME!,
compressed,
visitId,
'store_front',
process.env.S3_ENDPOINT_URL
)
console.log('Photo uploaded:', result.url)
return result
}Upload File Attachment
import { fileToBase64, uploadAttachment } from '@next-core/storage'
async function handleFileUpload(file: File) {
// 1. Convert to base64
const base64 = await fileToBase64(file)
// 2. Upload to S3
const result = await uploadAttachment(
s3,
bucket,
base64,
file.name,
file.type,
'sfa.visit',
123,
endpoint
)
return result.url
}Batch Photo Upload
import { compressPhotoBatch, uploadToS3 } from '@next-core/storage'
async function uploadMultiplePhotos(photos: string[]) {
// 1. Compress all photos in parallel
const compressed = await compressPhotoBatch(photos, 300)
// 2. Upload all compressed photos
const uploads = compressed.map((photo, i) =>
uploadToS3(s3, bucket, photo, `photo-${i}.jpg`, 'image/jpeg', 'batch')
)
const results = await Promise.all(uploads)
return results
}TypeScript Types
import type {
S3ClientConfig,
UploadResult,
VisitPhotoUploadResult,
PhotoCompressionOptions,
StorageQuota,
} from '@next-core/storage'Best Practices
1. Always Compress Photos
Mobile photos can be 3-5MB. Always compress before upload:
// ❌ Bad: Upload 3MB photo
await uploadToS3(s3, bucket, rawPhoto, ...)
// ✅ Good: Compress to 400KB first
const compressed = await compressPhoto(rawPhoto, 500)
await uploadToS3(s3, bucket, compressed, ...)2. Use Organized Folder Structure
// ✅ Good: Organized by purpose and ID
visits/123/photos/checkin.jpg
attachments/res.partner/456/contract.pdf
documents/2025/10/invoice-001.pdf3. Validate Config on Startup
// In your app initialization
if (!validateS3Config({
endpoint: process.env.S3_ENDPOINT_URL,
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
bucket: process.env.S3_BUCKET_NAME,
})) {
throw new Error('Invalid S3 configuration')
}4. Handle Errors Gracefully
try {
const result = await uploadToS3(...)
return result
} catch (error) {
console.error('Upload failed:', error)
// Fallback: Store locally for retry
await storeInLocalStorage(photo)
throw error
}S3-Compatible Services
This package works with:
- ✅ AWS S3 - Amazon's object storage
- ✅ MinIO - Self-hosted S3-compatible storage
- ✅ Hetzner Storage Box - German provider
- ✅ DigitalOcean Spaces - DO's object storage
- ✅ Wasabi - Hot cloud storage
- ✅ Backblaze B2 - Cloud storage with S3 API
License
MIT
Related Packages
- @next-core/ui - UI components and themes
- @next-core/auth - Authentication
- @next-core/odoo-api - Odoo integration
Built with ❤️ by ABC Food Development Team
