managarten/packages/shared-storage
Wuesteon 5b7d3c649b 🔧 chore: enforce monorepo best practices with automated validation
Fix critical issues and add validation to prevent future violations:

**Fixes:**
- Remove turbo recursion in 5 app packages (infinite loop risk)
- Add "private": true to 11 packages (prevent accidental publishing)
- Rename @mana-core/nestjs-integration → @manacore/nestjs-integration
- Remove prepublishOnly scripts from 3 private packages

**New:**
- Add scripts/validate-monorepo.mjs with 4 critical checks
- Add validate:monorepo command to package.json
- Integrate validation into CI pipeline (.github/workflows/ci.yml)
- Document validation in CLAUDE.md

All 80 package.json files now pass validation 

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-25 17:57:00 +01:00
..
src feat(storage): unified single-bucket architecture with Hetzner S3 2025-12-16 01:29:11 +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.

Architecture

All apps use a single unified bucket with folder structure:

manacore-storage/
├── {userId}/
│   ├── picture/...      # Picture app files
│   ├── chat/...         # Chat attachments
│   ├── manadeck/...     # Card assets
│   ├── contacts/...     # Contact avatars
│   └── ...

Setup

Local Development

# Start MinIO with Docker
pnpm docker:up

# MinIO Console: http://localhost:9001
# Username: minioadmin
# Password: minioadmin

Production (Hetzner Object Storage)

  1. Create Hetzner Object Storage in Hetzner Cloud Console
  2. Generate S3 credentials (Access Key + Secret Key)
  3. Run the setup script:
export S3_ENDPOINT="https://fsn1.your-objectstorage.com"
export S3_ACCESS_KEY="your-access-key"
export S3_SECRET_KEY="your-secret-key"
./scripts/setup-hetzner-storage.sh

Usage

import {
  createUnifiedStorage,
  generateStorageKey,
  getContentType,
  APPS,
} from '@manacore/shared-storage';

// Create storage client
const storage = createUnifiedStorage();

// Generate a key for a user's file
const key = generateStorageKey('user-123', APPS.PICTURE, 'photo.jpg');
// => 'user-123/picture/a1b2c3d4-uuid.jpg'

// Upload a file
const result = await storage.upload(key, imageBuffer, {
  contentType: getContentType('photo.jpg'),
  public: true,
});

console.log(result.url);
// => 'http://localhost:9000/manacore-storage/user-123/picture/uuid.jpg'

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

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

// List files for a user's app
const files = await storage.list('user-123/picture/');

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

Available Apps

import { APPS } from '@manacore/shared-storage';

APPS.PICTURE    // 'picture'
APPS.CHAT       // 'chat'
APPS.MANADECK   // 'manadeck'
APPS.NUTRIPHI   // 'nutriphi'
APPS.PRESI      // 'presi'
APPS.CALENDAR   // 'calendar'
APPS.CONTACTS   // 'contacts'
APPS.STORAGE    // 'storage'
APPS.MAIL       // 'mail'
APPS.INVENTORY  // 'inventory'
APPS.MANACORE   // 'manacore'

Key Generation Utilities

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

// Recommended: App-scoped key
generateStorageKey('user-123', 'picture', 'photo.jpg');
// => 'user-123/picture/uuid.jpg'

// With subfolder
generateStorageKey('user-123', 'chat', 'doc.pdf', 'attachments');
// => 'user-123/chat/attachments/uuid.pdf'

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

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

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

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
MANACORE_STORAGE_PUBLIC_URL=http://localhost:9000/manacore-storage

Production (Hetzner)

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

Docker Commands

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

# View MinIO logs
docker logs manacore-minio

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