managarten/packages/shared-storage
Wuesteon 2cfa09c84d feat(storage): add MinIO local storage and @manacore/shared-storage package
- Add MinIO service to docker-compose.dev.yml with auto-bucket initialization
- Create @manacore/shared-storage package with S3-compatible client
- Add factory functions for each project (Picture, Chat, ManaDeck, etc.)
- Include file utilities (generateFileKey, getContentType, validators)
- Update environment variables for S3/MinIO configuration
- Document storage architecture in CLAUDE.md

Local dev uses MinIO, production will use Hetzner Object Storage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 01:00:42 +01:00
..
src feat(storage): add MinIO local storage and @manacore/shared-storage package 2025-12-02 01:00:42 +01:00
package.json feat(storage): add MinIO local storage and @manacore/shared-storage package 2025-12-02 01:00:42 +01:00
README.md feat(storage): add MinIO local storage and @manacore/shared-storage package 2025-12-02 01:00:42 +01:00
tsconfig.json feat(storage): add MinIO local storage and @manacore/shared-storage package 2025-12-02 01:00:42 +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-images Picture Generated AI images
chat-files Chat User file uploads
manadeck-assets ManaDeck Card/deck assets
nutriphi-meals NutriPhi Meal photos
presi-slides Presi Presentation slides

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-images/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,
} 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-images.fsn1.your-objectstorage.com
NUTRIPHI_S3_PUBLIC_URL=https://nutriphi-meals.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