managarten/packages/shared-storage
Till-JS c2842e2546 feat(auth): add avatar upload with S3/MinIO and subscription plans seed
- Add StorageModule for avatar uploads via S3/MinIO
- Create presigned URL endpoint for direct browser uploads
- Create direct upload endpoint (multipart/form-data)
- Add manacore-storage bucket to shared-storage package
- Add manacore-storage bucket to docker-compose.dev.yml
- Create subscription plans seed script (pnpm db:seed:plans)
- Plans: Free (150 credits), Pro (2000/€9.99/mo), Enterprise (10000/€49/mo)
- Update TODO list with completed tasks
2026-02-13 23:06:24 +01:00
..
src feat(auth): add avatar upload with S3/MinIO and subscription plans seed 2026-02-13 23:06:24 +01:00
package.json 🔧 chore: enforce monorepo best practices with automated validation 2025-12-25 17:57:00 +01:00
README.md feat(storage): unified single-bucket architecture with Hetzner S3 2025-12-16 01:29:11 +01:00
tsconfig.json 🔧 fix(shared-storage): update build configuration for CommonJS 2025-12-02 14:40:55 +01:00

@manacore/shared-storage

S3-compatible object storage client for the Manacore monorepo. Uses MinIO for local development and Hetzner Object Storage in production.

Setup

Local Development

  1. Start MinIO with Docker:
pnpm docker:up
  1. Access MinIO Console at http://localhost:9001
    • Username: minioadmin
    • Password: minioadmin

Pre-created Buckets

The following buckets are automatically created:

Bucket Project Purpose
picture-storage Picture Generated AI images
chat-storage Chat User file uploads
manadeck-storage ManaDeck Card/deck assets
nutriphi-storage NutriPhi Meal photos
presi-storage Presi Presentation slides
calendar-storage Calendar Calendar attachments
contacts-storage Contacts Contact avatars/files
storage-storage Storage Cloud drive files

Usage

Basic Usage

import { createPictureStorage, generateUserFileKey, getContentType } from '@manacore/shared-storage';

// Create client for Picture project
const storage = createPictureStorage();

// Upload a file
const key = generateUserFileKey('user-123', 'avatar.png');
const result = await storage.upload(key, imageBuffer, {
  contentType: getContentType('avatar.png'),
  public: true,
});

console.log(result.url); // http://localhost:9000/picture-storage/users/user-123/uuid.png

// Download a file
const buffer = await storage.download(key);

// Delete a file
await storage.delete(key);

// List files
const files = await storage.list('users/user-123/');

// Generate presigned URLs
const uploadUrl = await storage.getUploadUrl('temp/upload.png', { expiresIn: 3600 });
const downloadUrl = await storage.getDownloadUrl(key, { expiresIn: 3600 });

Custom Configuration

import { createStorageClient, BUCKETS } from '@manacore/shared-storage';

// Override default config
const storage = createStorageClient(BUCKETS.PICTURE, {
  endpoint: 'https://fsn1.your-objectstorage.com',
  region: 'fsn1',
  accessKeyId: process.env.HETZNER_ACCESS_KEY,
  secretAccessKey: process.env.HETZNER_SECRET_KEY,
  forcePathStyle: false,
});

Available Factory Functions

import {
  createPictureStorage,
  createChatStorage,
  createManaDeckStorage,
  createNutriPhiStorage,
  createPresiStorage,
  createCalendarStorage,
  createContactsStorage,
  createStorageStorage,
} from '@manacore/shared-storage';

Environment Variables

Local Development (MinIO)

Already configured in .env.development:

S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin

Production (Hetzner Object Storage)

S3_ENDPOINT=https://fsn1.your-objectstorage.com
S3_REGION=fsn1
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key

# Optional: public URLs for CDN access
PICTURE_STORAGE_PUBLIC_URL=https://picture-storage.fsn1.your-objectstorage.com
NUTRIPHI_S3_PUBLIC_URL=https://nutriphi-storage.fsn1.your-objectstorage.com

Utilities

import {
  generateFileKey,
  generateUserFileKey,
  getContentType,
  validateFileSize,
  validateFileExtension,
  IMAGE_EXTENSIONS,
  DOCUMENT_EXTENSIONS,
} from '@manacore/shared-storage';

// Generate unique file key
const key = generateFileKey('photo.jpg', 'uploads', '2024');
// => 'uploads/2024/uuid.jpg'

// User-scoped key
const userKey = generateUserFileKey('user-123', 'avatar.png', 'avatars');
// => 'users/user-123/avatars/uuid.png'

// Get MIME type
const contentType = getContentType('image.png'); // => 'image/png'

// Validate file
const isValidSize = validateFileSize(fileSize, 10); // max 10MB
const isValidType = validateFileExtension('photo.jpg', IMAGE_EXTENSIONS);

Docker Commands

# Start all infrastructure (Postgres, Redis, MinIO)
pnpm docker:up

# Start only database services (no MinIO)
pnpm docker:up:db

# View MinIO logs
docker logs manacore-minio

# View bucket init logs
docker logs manacore-minio-init