Compare commits

...

4 Commits

Author SHA1 Message Date
Oleg Proskurin 839eeac2c2 Merge branch 'init-monorepo' 2025-09-29 22:54:11 +07:00
Oleg Proskurin 9b0b74f686 chore: update docs 2025-09-29 22:53:44 +07:00
Oleg Proskurin f572428a87 fix: image generation 2025-09-29 22:49:32 +07:00
Oleg Proskurin babcbe29db feat: move to monorepo 2025-09-29 21:58:43 +07:00
49 changed files with 4267 additions and 387 deletions

4
.gitignore vendored
View File

@ -9,6 +9,9 @@ pnpm-debug.log*
dist/
build/
*.tsbuildinfo
apps/*/dist/
apps/*/.next/
.next/
# Environment files
.env
@ -35,6 +38,7 @@ Thumbs.db
# Logs
logs/
apps/*/logs/
*.log
# Runtime data

132
CLAUDE.md
View File

@ -4,44 +4,81 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
Banatie is a REST API service for AI-powered image generation using the Gemini Flash Image model. It's built with Express.js and TypeScript, providing endpoints to generate images from text prompts with optional reference images.
Banatie is a comprehensive monorepo for AI-powered image generation platform featuring multiple applications:
- **API Service** (`apps/api-service`) - Core REST API using Express.js and TypeScript for image generation with Gemini AI
- **Landing Page** (`apps/landing`) - Next.js public website with demo functionality
- **Studio Platform** (`apps/studio`) - Next.js SaaS application with authentication, billing, and subscriptions. Based on https://github.com/nextjs/saas-starter
- **Admin Dashboard** (`apps/admin`) - Next.js administration interface for monitoring and management
The project uses MinIO for object storage and PostgreSQL for data persistence, all orchestrated with Docker Compose.
## Development Commands
use `docker compose` command for using docker-compose service (v3 version)
Use `docker compose` command for Docker services (v3 version).
### Core Development
- `pnpm dev` - Start development server with auto-reload using tsx
- `pnpm start` - Start production server (runs build first)
- `pnpm build` - Build TypeScript to JavaScript in ./dist
- `pnpm typecheck` - Run TypeScript type checking without emitting files
### Infrastructure
### Code Quality
- `pnpm lint` - Run ESLint on all TypeScript files in src/
- `pnpm lint:fix` - Run ESLint with auto-fix
- `pnpm format` - Format code with Prettier
- `pnpm format:check` - Check if code is formatted correctly
- `docker compose up -d postgres minio storage-init` - Start database and storage services
- `docker compose up -d` - Start all services including the API app container
- `docker compose down` - Stop all services
### Testing
- `pnpm test` - Run Jest tests
- `pnpm test:watch` - Run tests in watch mode
- `pnpm test:coverage` - Run tests with coverage report
### Monorepo Commands (from root)
- `pnpm dev` - Start all applications in development mode
- `pnpm dev:api` - Start API service only (`apps/api-service`)
- `pnpm dev:landing` - Start landing page only (`apps/landing`)
- `pnpm dev:studio` - Start studio platform only (`apps/studio`)
- `pnpm dev:admin` - Start admin dashboard only (`apps/admin`)
### Build & Production
- `pnpm build` - Build all applications
- `pnpm build:api` - Build API service only
- `pnpm start:api` - Start API service in production mode
- `pnpm start:landing` - Start landing page in production mode
### Code Quality (runs across all apps)
- `pnpm lint` - Run ESLint on all applications
- `pnpm typecheck` - Run TypeScript checking on all applications
- `pnpm test` - Run tests (currently API service only)
- `pnpm clean` - Clean all build outputs and dependencies
## Architecture
### Core Structure
### Monorepo Structure
```
banatie-service/
├── apps/
│ ├── api-service/ # Express.js REST API (TypeScript)
│ ├── landing/ # Next.js landing page
│ ├── studio/ # Next.js SaaS platform
│ └── admin/ # Next.js admin dashboard
├── data/ # Docker volume data (postgres, minio)
├── docker-compose.yml # Infrastructure services
├── pnpm-workspace.yaml # Workspace configuration
└── package.json # Root workspace scripts
```
### API Service Architecture (`apps/api-service/`)
- **Express App**: Configured in `src/app.ts` with middleware, CORS, and route mounting
- **Server Entry**: `src/server.ts` starts the HTTP server
- **Image Generation**: `src/services/ImageGenService.ts` handles Gemini AI integration
- **Storage**: `src/services/MinioStorageService.ts` handles file uploads to MinIO
- **Route Handling**: `src/routes/generate.ts` contains the main API endpoint logic
### Middleware Stack
### Middleware Stack (API Service)
- `src/middleware/upload.ts` - Multer configuration for file uploads (max 3 files, 5MB each)
- `src/middleware/validation.ts` - Express-validator for request validation
- `src/middleware/errorHandler.ts` - Centralized error handling and 404 responses
### TypeScript Configuration
- Path aliases configured in tsconfig.json:
### TypeScript Configuration (API Service)
- Path aliases configured in `apps/api-service/tsconfig.json`:
- `@/*` maps to `src/*`
- `@/types/*` maps to `src/types/*`
- `@/services/*` maps to `src/services/*`
@ -49,30 +86,66 @@ use `docker compose` command for using docker-compose service (v3 version)
- `@/routes/*` maps to `src/routes/*`
- `@/utils/*` maps to `src/utils/*`
### File Handling
- **Uploads**: Temporary files stored in `./uploads/temp`
- **Results**: Generated images saved to `./results`
- **Logs**: Application logs in `./logs`
### Storage & Data
- **MinIO**: Object storage for generated images and uploads (port 9000)
- **PostgreSQL**: Database for user data, metadata (port 5434)
- **File Organization**: `orgId/projectId/category/year-month/filename.ext`
## Environment Configuration
### Root Environment (`.env.docker`)
- `MINIO_ROOT_USER` - MinIO admin username
- `MINIO_ROOT_PASSWORD` - MinIO admin password
### API Service Environment (`apps/api-service/.env`)
Required environment variables:
- `GEMINI_API_KEY` - Google Gemini API key (required)
- `MINIO_ENDPOINT` - MinIO endpoint (`localhost:9000` for local dev, `minio:9000` for Docker)
- `MINIO_ACCESS_KEY` - MinIO service account key
- `MINIO_SECRET_KEY` - MinIO service account secret
- `MINIO_BUCKET_NAME` - Storage bucket name (default: `banatie`)
- `PORT` - Server port (default: 3000)
- `NODE_ENV` - Environment mode
- `CORS_ORIGIN` - CORS origin setting (default: *)
- `CORS_ORIGIN` - CORS origin setting (default: \*)
## Key Dependencies
### API Service
- **@google/genai** - Google Gemini AI client
- **express** v5 - Web framework
- **multer** - File upload handling
- **minio** - MinIO client for object storage
- **express-validator** - Request validation
- **winston** - Logging
- **helmet** - Security middleware
- **express-rate-limit** - Rate limiting
## API Endpoints
### Frontend Apps (Next.js)
- **next** - React framework
- **tailwindcss** - Utility-first CSS framework
- **typescript** - Type safety
- **supabase** - Authentication and database (studio app)
- **stripe** - Payment processing (studio app)
## Service Ports
| Service | Port | Description |
| --------------- | ---- | ------------------ |
| API Service | 3000 | REST API endpoints |
| Landing Page | 3001 | Public website |
| Studio Platform | 3002 | SaaS application |
| Admin Dashboard | 3003 | Administration |
| PostgreSQL | 5434 | Database |
| MinIO API | 9000 | Object storage |
| MinIO Console | 9001 | Storage management |
## API Endpoints (API Service)
- `GET /health` - Health check with uptime and status
- `GET /api/info` - API information and limits
@ -80,8 +153,9 @@ Required environment variables:
## Development Notes
- Uses pnpm as package manager (required >= 8.0.0)
- Uses pnpm workspaces for monorepo management (required >= 8.0.0)
- Node.js >= 18.0.0 required
- TypeScript with strict mode enabled
- TypeScript with strict mode enabled across all apps
- ESLint configured with TypeScript and Prettier integration
- Jest for testing with ts-jest preset
- Jest for testing with ts-jest preset (API service)
- Each app can be developed and deployed independently

289
README.md
View File

@ -1,92 +1,142 @@
# Banatie - Nano Banana Image Generation Service
# Banatie - AI Image Generation Service
A REST API service for AI-powered image generation using the Gemini Flash Image model. Banatie provides a simple HTTP interface for generating high-quality images from text prompts with optional reference images.
A comprehensive monorepo for the Banatie AI image generation platform, featuring multiple applications for different use cases.
## 🚀 Quick Start
## Architecture Overview
```
banatie-service/
├── apps/
│ ├── api-service/ # REST API for image generation (Express.js + TypeScript)
│ ├── landing/ # Landing page with demo (Next.js)
│ ├── studio/ # SaaS platform with billing (Next.js + Supabase + Stripe)
│ └── admin/ # Administration dashboard (Next.js)
├── data/ # Docker volumes (postgres, minio)
├── docker-compose.yml # Infrastructure services
└── package.json # Workspace scripts
```
## Applications
### 🚀 API Service (`apps/api-service`)
- **Port**: 3000
- **Tech**: Express.js, TypeScript, Gemini AI
- **Purpose**: Core REST API for AI image generation
- **Features**: Image generation, file upload, rate limiting, logging
### 🌐 Landing Page (`apps/landing`)
- **Port**: 3001
- **Tech**: Next.js 14, Tailwind CSS
- **Purpose**: Public landing page with demo
- **Features**: Service overview, image generation demo
### 🏢 Studio (`apps/studio`)
- **Port**: 3002
- **Tech**: Next.js 14, Supabase, Stripe
- **Purpose**: SaaS platform for paid users
- **Features**: Authentication, billing, subscriptions, usage tracking
### 🔧 Admin (`apps/admin`)
- **Port**: 3003
- **Tech**: Next.js 14, Dashboard components
- **Purpose**: Service administration and monitoring
- **Features**: System monitoring, user management, analytics
## Quick Start
### Prerequisites
- Node.js >= 18.0.0
- pnpm >= 8.0.0
- Gemini API key from [Google AI Studio](https://aistudio.google.com/)
- Docker & Docker Compose
### Installation
### Development Setup
```bash
# Clone the repository
# Clone and install dependencies
git clone <repository-url>
cd banatie
# Install dependencies
cd banatie-service
pnpm install
# Setup environment variables
cp .env.example .env
# Edit .env with your Gemini API key
```
# Start infrastructure (PostgreSQL + MinIO)
docker compose up -d postgres minio storage-init
### Development
```bash
# Start development server with auto-reload
# Start all applications in development mode
pnpm dev
# Start production server
pnpm start
# Build for production
pnpm build
# Or start individual apps
pnpm dev:api # API Service on :3000
pnpm dev:landing # Landing Page on :3001
pnpm dev:studio # Studio Platform on :3002
pnpm dev:admin # Admin Dashboard on :3003
```
## 📝 Environment Configuration
Copy `.env.example` to `.env` and configure:
### Production Deployment
```bash
# Required
GEMINI_API_KEY=your_gemini_api_key_here
# Build all applications
pnpm build
# Optional (with defaults)
PORT=3000
NODE_ENV=development
CORS_ORIGIN=*
MAX_FILE_SIZE=5242880 # 5MB
MAX_FILES=3
RESULTS_DIR=./results
UPLOADS_DIR=./uploads/temp
LOG_LEVEL=info
# Start infrastructure
docker compose up -d
# Start production servers
pnpm start:api
pnpm start:landing
pnpm start:studio
pnpm start:admin
```
## 🔌 API Endpoints
## Development Commands
### Health Check
```
GET /health
```
Returns server status and uptime.
```bash
# Install dependencies for all apps
pnpm install
### API Information
# Development (all apps in parallel)
pnpm dev
# Build all apps
pnpm build
# Type checking
pnpm typecheck
# Linting
pnpm lint
# Testing (API service only)
pnpm test
# Clean all build outputs
pnpm clean
```
GET /api/info
```
Returns API details and configuration.
## Environment Configuration
Each application has its own environment configuration:
- **Root**: `.env.docker` (for Docker services)
- **API Service**: `apps/api-service/.env`
- **Studio**: `apps/studio/.env.local`
- **Admin**: `apps/admin/.env.local`
See individual app README files for specific environment variables.
## Services & Ports
| Service | Port | Description |
|---------|------|-------------|
| API Service | 3000 | Core REST API |
| Landing Page | 3001 | Public website |
| Studio Platform | 3002 | SaaS application |
| Admin Dashboard | 3003 | Administration |
| PostgreSQL | 5434 | Database |
| MinIO API | 9000 | Object storage |
| MinIO Console | 9001 | Storage admin |
## API Usage
### Generate Image
```
POST /api/generate
Content-Type: multipart/form-data
```
**Parameters:**
- `prompt` (string, required): Text description for image generation
- `filename` (string, optional): Custom filename for the generated image
- `referenceImages` (files, optional): Up to 3 reference images (5MB max each)
**Supported formats:** PNG, JPEG, JPG, WebP
## 📖 Usage Examples
### cURL
```bash
# Basic text-to-image
curl -X POST http://localhost:3000/api/generate \
@ -99,114 +149,15 @@ curl -X POST http://localhost:3000/api/generate \
-F "referenceImages=@./reference.jpg"
```
### JavaScript/Fetch
```javascript
const formData = new FormData();
formData.append('prompt', 'A futuristic cityscape at sunset');
formData.append('filename', 'futuristic_city');
See `apps/api-service/README.md` for detailed API documentation.
const response = await fetch('http://localhost:3000/api/generate', {
method: 'POST',
body: formData
});
## Contributing
const result = await response.json();
console.log(result);
```
1. Each app has its own package.json and dependencies
2. Use the root workspace commands for multi-app operations
3. Follow the existing code style and patterns
4. Run `pnpm typecheck` and `pnpm lint` before committing
### Response Format
```json
{
"success": true,
"message": "Image generated successfully",
"data": {
"filename": "generated_image_123.png",
"filepath": "./results/generated_image_123.png",
"description": "Generated image description",
"model": "gemini-2.0-flash-exp",
"generatedAt": "2024-01-01T12:00:00.000Z"
}
}
```
## License
## 🛠️ Development Scripts
```bash
# Development
pnpm dev # Start with auto-reload
pnpm build # Build for production
pnpm start # Start production server
# Code Quality
pnpm typecheck # TypeScript type checking
pnpm lint # ESLint code linting
pnpm lint:fix # Fix ESLint issues
pnpm format # Format code with Prettier
pnpm format:check # Check code formatting
# Testing
pnpm test # Run tests
pnpm test:watch # Run tests in watch mode
pnpm test:coverage # Run tests with coverage
```
## 🏗️ Project Structure
```
banatie/
├── src/
│ ├── middleware/ # Express middleware
│ ├── routes/ # API route handlers
│ ├── services/ # Business logic services
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # Utility functions
│ ├── app.ts # Express app configuration
│ └── server.ts # Server entry point
├── tests/ # Test files
├── results/ # Generated images output
├── uploads/ # Temporary file uploads
└── logs/ # Application logs
```
## 🔧 Configuration
### File Limits
- **Max file size**: 5MB per image (configurable)
- **Max files**: 3 reference images per request (configurable)
- **Request size**: 10MB total request limit
### Supported Image Formats
- PNG
- JPEG/JPG
- WebP
## 🚦 Error Handling
The API provides detailed error responses:
**Validation Errors (400):**
- Missing required parameters
- Invalid file formats
- File size exceeded
- Too many files
**Server Errors (500):**
- Missing API key configuration
- Image generation failures
- Internal processing errors
## 📄 License
MIT License - see LICENSE file for details.
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Run tests and linting
5. Submit a pull request
## 📞 Support
For issues and questions, please open an issue on the GitHub repository.
MIT License - see individual app directories for more details.

18
apps/admin/.env.example Normal file
View File

@ -0,0 +1,18 @@
# Banatie API Configuration
BANATIE_API_URL=http://localhost:3000
# Database Configuration (Direct admin access)
POSTGRES_URL=postgresql://banatie_user:banatie_secure_password@localhost:5434/banatie
# MinIO Storage Configuration
MINIO_ENDPOINT=http://localhost:9000
MINIO_ACCESS_KEY=your_minio_access_key
MINIO_SECRET_KEY=your_minio_secret_key
# Admin Authentication
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_secure_admin_password
# Next.js Configuration
NEXTAUTH_URL=http://localhost:3003
NEXTAUTH_SECRET=your_nextauth_secret

66
apps/admin/README.md Normal file
View File

@ -0,0 +1,66 @@
# Banatie Admin
Administration dashboard for managing the entire Banatie AI image generation service.
## Features
- 📊 **System Monitoring**: Real-time service health and performance metrics
- 👥 **User Management**: Manage users, subscriptions, and permissions
- 🖼️ **Content Management**: Monitor generated images and usage patterns
- 🔧 **Service Control**: Start/stop services, update configurations
- 📈 **Analytics**: Detailed usage analytics and reporting
- 🚨 **Alerts**: System alerts and notifications
## Tech Stack
- **Framework**: Next.js 14 with App Router
- **Database**: Direct PostgreSQL connection for admin operations
- **Storage**: MinIO admin interface integration
- **Monitoring**: Integration with API service metrics
- **Styling**: Tailwind CSS with admin-focused components
- **Charts**: Recharts for data visualization
## Development
```bash
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Start development server
pnpm dev
```
## Environment Variables
```env
# Banatie API
BANATIE_API_URL=http://localhost:3000
# Database (Direct admin access)
POSTGRES_URL=postgresql://user:password@localhost:5434/banatie
# Storage
MINIO_ENDPOINT=http://localhost:9000
MINIO_ACCESS_KEY=admin_access_key
MINIO_SECRET_KEY=admin_secret_key
# Admin Authentication
ADMIN_USERNAME=admin
ADMIN_PASSWORD=secure_password
```
## TODO
- [ ] Implement real-time metrics dashboard
- [ ] Add user management interface
- [ ] Create service control panel
- [ ] Add database admin tools
- [ ] Implement log viewer
- [ ] Add configuration management
- [ ] Create backup/restore functionality
- [ ] Add alert system
- [ ] Implement audit logging
- [ ] Add API key management

16
apps/admin/next.config.js Normal file
View File

@ -0,0 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ['localhost'],
},
env: {
BANATIE_API_URL: process.env.BANATIE_API_URL || 'http://localhost:3000',
POSTGRES_URL: process.env.POSTGRES_URL,
MINIO_ENDPOINT: process.env.MINIO_ENDPOINT,
},
}
module.exports = nextConfig

37
apps/admin/package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "@banatie/admin",
"version": "1.0.0",
"description": "Banatie Admin - Administration dashboard for managing the entire Banatie service",
"private": true,
"scripts": {
"dev": "next dev -p 3003",
"build": "next build",
"start": "next start -p 3003",
"lint": "next lint",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"typescript": "^5.9.2",
"recharts": "^2.12.0",
"lucide-react": "^0.400.0"
},
"devDependencies": {
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.0",
"@tailwindcss/forms": "^0.5.0",
"@headlessui/react": "^2.0.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
}
}

View File

@ -0,0 +1,22 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Banatie Admin - Service Administration',
description: 'Administration dashboard for managing the Banatie AI image generation service',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className="min-h-screen bg-gray-100">
<div className="min-h-screen">
{children}
</div>
</body>
</html>
)
}

174
apps/admin/src/app/page.tsx Normal file
View File

@ -0,0 +1,174 @@
export default function AdminDashboard() {
return (
<div className="min-h-screen bg-gray-100">
{/* Header */}
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<h1 className="text-2xl font-bold text-gray-900">
Banatie Admin
</h1>
</div>
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-500">System Status: Online</span>
</div>
</div>
</div>
</header>
{/* Main Content */}
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* Stats Cards */}
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-blue-500 rounded-md flex items-center justify-center">
<span className="text-white text-sm font-medium">API</span>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">
API Requests
</dt>
<dd className="text-lg font-medium text-gray-900">
1,234
</dd>
</dl>
</div>
</div>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-green-500 rounded-md flex items-center justify-center">
<span className="text-white text-sm font-medium">IMG</span>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">
Images Generated
</dt>
<dd className="text-lg font-medium text-gray-900">
567
</dd>
</dl>
</div>
</div>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-yellow-500 rounded-md flex items-center justify-center">
<span className="text-white text-sm font-medium">USR</span>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">
Active Users
</dt>
<dd className="text-lg font-medium text-gray-900">
89
</dd>
</dl>
</div>
</div>
</div>
</div>
<div className="bg-white overflow-hidden shadow rounded-lg">
<div className="p-5">
<div className="flex items-center">
<div className="flex-shrink-0">
<div className="w-8 h-8 bg-red-500 rounded-md flex items-center justify-center">
<span className="text-white text-sm font-medium">ERR</span>
</div>
</div>
<div className="ml-5 w-0 flex-1">
<dl>
<dt className="text-sm font-medium text-gray-500 truncate">
Errors (24h)
</dt>
<dd className="text-lg font-medium text-gray-900">
3
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
{/* Services Status */}
<div className="mt-8">
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">
Service Status
</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-900">API Service</span>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
Healthy
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-900">PostgreSQL Database</span>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
Connected
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-900">MinIO Storage</span>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
Available
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-gray-900">Gemini AI</span>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
Rate Limited
</span>
</div>
</div>
</div>
</div>
</div>
{/* Coming Soon Notice */}
<div className="mt-8">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex">
<div className="ml-3">
<h3 className="text-sm font-medium text-blue-800">
Admin Dashboard Under Development
</h3>
<div className="mt-2 text-sm text-blue-700">
<p>
This admin dashboard is being developed to provide comprehensive
monitoring and management capabilities for the Banatie service.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
)
}

31
apps/admin/tsconfig.json Normal file
View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"],
"@/types/*": ["./src/types/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@ -0,0 +1,72 @@
{
"name": "@banatie/api-service",
"version": "1.0.0",
"description": "Nano Banana Image Generation Service - REST API for AI-powered image generation using Gemini Flash Image model",
"main": "dist/server.js",
"scripts": {
"dev": "tsx --watch src/server.ts",
"start": "node dist/server.js",
"build": "tsc",
"typecheck": "tsc --noEmit",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"format": "prettier --write src/**/*.ts",
"format:check": "prettier --check src/**/*.ts",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"prestart": "npm run build"
},
"keywords": [
"image-generation",
"ai",
"gemini",
"nano-banana",
"rest-api",
"express",
"typescript"
],
"author": "",
"license": "MIT",
"packageManager": "pnpm@10.11.0",
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"dependencies": {
"@google/genai": "^1.17.0",
"cors": "^2.8.5",
"dotenv": "^17.2.2",
"express": "^5.1.0",
"express-rate-limit": "^7.4.1",
"express-validator": "^7.2.0",
"helmet": "^8.0.0",
"mime": "3.0.0",
"minio": "^8.0.6",
"multer": "^2.0.2",
"winston": "^3.17.0"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^29.5.14",
"@types/multer": "^2.0.0",
"@types/node": "^24.3.1",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^8.18.2",
"@typescript-eslint/parser": "^8.18.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"jest": "^29.7.0",
"nodemon": "^3.1.9",
"prettier": "^3.4.2",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}

View File

@ -8,6 +8,7 @@ import {
ReferenceImage,
} from "../types/api";
import { StorageFactory } from "./StorageFactory";
import { UploadResult } from "./StorageService";
export class ImageGenService {
private ai: GoogleGenAI;
@ -27,20 +28,18 @@ export class ImageGenService {
async generateImage(
options: ImageGenerationOptions,
): Promise<ImageGenerationResult> {
const { prompt, filename, referenceImages, orgId, projectId, userId } = options;
const { prompt, filename, referenceImages, orgId, projectId, userId } =
options;
const timestamp = new Date().toISOString();
// Use default values if not provided
const finalOrgId = orgId || process.env['DEFAULT_ORG_ID'] || 'default';
const finalProjectId = projectId || process.env['DEFAULT_PROJECT_ID'] || 'main';
const finalUserId = userId || process.env['DEFAULT_USER_ID'] || 'system';
const finalOrgId = orgId || process.env["DEFAULT_ORG_ID"] || "default";
const finalProjectId =
projectId || process.env["DEFAULT_PROJECT_ID"] || "main";
const finalUserId = userId || process.env["DEFAULT_USER_ID"] || "system";
console.log(
`[${timestamp}] Starting image generation: "${prompt.substring(0, 50)}..." for ${finalOrgId}/${finalProjectId}`,
);
try {
// First try the primary model (Nano Banana)
const result = await this.tryGeneration({
model: this.primaryModel,
config: { responseModalities: ["IMAGE", "TEXT"] },
@ -50,17 +49,13 @@ export class ImageGenService {
projectId: finalProjectId,
userId: finalUserId,
...(referenceImages && { referenceImages }),
modelName: "Nano Banana",
modelName: "Primary Model",
});
if (result.success) {
return result;
}
// Fallback to Imagen 4
console.log(
`[${new Date().toISOString()}] Primary model failed, trying fallback (Imagen 4)...`,
);
return await this.tryGeneration({
model: this.fallbackModel,
@ -71,13 +66,9 @@ export class ImageGenService {
projectId: finalProjectId,
userId: finalUserId,
...(referenceImages && { referenceImages }),
modelName: "Imagen 4",
modelName: "Fallback Model",
});
} catch (error) {
console.error(
`[${new Date().toISOString()}] Image generation failed:`,
error,
);
return {
success: false,
model: "none",
@ -87,9 +78,6 @@ export class ImageGenService {
}
}
/**
* Try generation with a specific model
*/
private async tryGeneration(params: {
model: string;
config: { responseModalities: string[] };
@ -101,18 +89,22 @@ export class ImageGenService {
referenceImages?: ReferenceImage[];
modelName: string;
}): Promise<ImageGenerationResult> {
const { model, config, prompt, filename, orgId, projectId, userId, referenceImages, modelName } =
params;
const {
model,
config,
prompt,
filename,
orgId,
projectId,
userId,
referenceImages,
modelName,
} = params;
try {
// Build content parts for the API request
const contentParts: any[] = [];
// Add reference images if provided
if (referenceImages && referenceImages.length > 0) {
console.log(
`[${new Date().toISOString()}] Adding ${referenceImages.length} reference image(s)`,
);
for (const refImage of referenceImages) {
contentParts.push({
@ -124,7 +116,6 @@ export class ImageGenService {
}
}
// Add the text prompt
contentParts.push({
text: prompt,
});
@ -136,9 +127,6 @@ export class ImageGenService {
},
];
console.log(
`[${new Date().toISOString()}] Making API request to ${modelName} (${model})...`,
);
const response = await this.ai.models.generateContent({
model,
@ -146,9 +134,6 @@ export class ImageGenService {
contents,
});
console.log(
`[${new Date().toISOString()}] Response received from ${modelName}`,
);
if (
response.candidates &&
@ -157,7 +142,7 @@ export class ImageGenService {
) {
const content = response.candidates[0].content;
let generatedDescription = "";
let uploadResult = null;
let uploadResult: UploadResult | null = null;
for (let index = 0; index < (content.parts?.length || 0); index++) {
const part = content.parts?.[index];
@ -168,36 +153,30 @@ export class ImageGenService {
part.inlineData.mimeType || "",
);
const finalFilename = `${filename}.${fileExtension}`;
const contentType = part.inlineData.mimeType || `image/${fileExtension}`;
const contentType =
part.inlineData.mimeType || `image/${fileExtension}`;
console.log(
`[${new Date().toISOString()}] Uploading image to MinIO: ${finalFilename}`,
);
const buffer = Buffer.from(part.inlineData.data || "", "base64");
// Upload to MinIO storage
const storageService = StorageFactory.getInstance();
uploadResult = await storageService.uploadFile(
const result = (await storageService).uploadFile(
orgId,
projectId,
'generated',
"generated",
finalFilename,
buffer,
contentType
contentType,
);
console.log(
`[${new Date().toISOString()}] Image uploaded successfully: ${uploadResult.path}`,
);
uploadResult = await result;
} else if (part.text) {
generatedDescription = part.text;
console.log(
`[${new Date().toISOString()}] Generated description: ${part.text.substring(0, 100)}...`,
);
}
}
if (uploadResult && uploadResult.success) {
return {
success: true,
@ -216,10 +195,6 @@ export class ImageGenService {
error: "No image data received from API",
};
} catch (error) {
console.error(
`[${new Date().toISOString()}] ${modelName} generation failed:`,
error,
);
return {
success: false,
model: modelName,
@ -228,10 +203,6 @@ export class ImageGenService {
}
}
/**
* Validate reference images
*/
static validateReferenceImages(files: Express.Multer.File[]): {
valid: boolean;
error?: string;
@ -262,9 +233,6 @@ export class ImageGenService {
return { valid: true };
}
/**
* Convert Express.Multer.File[] to ReferenceImage[]
*/
static convertFilesToReferenceImages(
files: Express.Multer.File[],
): ReferenceImage[] {

View File

@ -312,7 +312,6 @@ export class MinioStorageService implements StorageService {
createdAt: obj.lastModified || new Date()
});
} catch (error) {
console.error(`Error processing file ${obj.name}:`, error);
}
});
@ -328,7 +327,6 @@ export class MinioStorageService implements StorageService {
filename: string;
} | null {
try {
// Key format: banatie/orgId/projectId/category/year-month/filename
const match = key.match(/^banatie\/([^/]+)\/([^/]+)\/(uploads|generated|references)\/[^/]+\/(.+)$/);
if (!match) {
@ -352,7 +350,6 @@ export class MinioStorageService implements StorageService {
}
}
// MISSING METHODS FROM INTERFACE
async fileExists(
orgId: string,
@ -376,7 +373,7 @@ export class MinioStorageService implements StorageService {
category: 'uploads' | 'generated' | 'references',
prefix?: string
): Promise<FileMetadata[]> {
this.validateFilePath(orgId, projectId, category, 'dummy.txt'); // Validate path components
this.validateFilePath(orgId, projectId, category, 'dummy.txt');
const basePath = `${orgId}/${projectId}/${category}/`;
const searchPrefix = prefix ? `${basePath}${prefix}` : basePath;
@ -406,7 +403,6 @@ export class MinioStorageService implements StorageService {
path: obj.name
});
} catch (error) {
console.error(`Error processing file ${obj.name}:`, error);
}
});

View File

@ -25,13 +25,11 @@ export class StorageFactory {
}
}
// Synchronous version for backward compatibility (with graceful degradation)
static getInstanceSync(): StorageService {
if (!this.instance) {
try {
this.instance = this.createStorageService();
} catch (error) {
console.error('Failed to create storage service:', error);
throw new Error('Storage service unavailable. Please check MinIO configuration.');
}
}
@ -45,18 +43,14 @@ export class StorageFactory {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempting to create storage service (attempt ${attempt}/${maxRetries})`);
const service = this.createStorageService();
// Test the connection by checking if bucket exists
await service.bucketExists();
console.log('Storage service created successfully');
return service;
} catch (error) {
console.error(`Storage service creation attempt ${attempt} failed:`, error);
if (attempt === maxRetries) {
throw new Error(
@ -65,9 +59,7 @@ export class StorageFactory {
);
}
// Exponential backoff
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Waiting ${delay}ms before retry...`);
await this.sleep(delay);
}
}
@ -98,11 +90,6 @@ export class StorageFactory {
);
}
console.log(`Initializing MinIO Storage Service:`);
console.log(` Endpoint: ${endpoint}`);
console.log(` Bucket: ${bucketName}`);
console.log(` SSL: ${useSSL}`);
console.log(` Public URL: ${publicUrl}`);
return new MinioStorageService(
endpoint,
@ -118,12 +105,10 @@ export class StorageFactory {
throw new Error(`Unsupported storage type: ${storageType}`);
}
} catch (error) {
console.error('Error creating storage service:', error);
throw error;
}
}
// Reset instance for testing
static resetInstance(): void {
this.instance = null;
this.initializationPromise = null;

View File

@ -1,4 +1,4 @@
import { Readable } from 'stream';
import { Readable } from "stream";
export interface FileMetadata {
filename: string;
@ -42,10 +42,10 @@ export interface StorageService {
uploadFile(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
category: "uploads" | "generated" | "references",
filename: string,
buffer: Buffer,
contentType: string
contentType: string,
): Promise<UploadResult>;
/**
@ -58,8 +58,8 @@ export interface StorageService {
downloadFile(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
filename: string
category: "uploads" | "generated" | "references",
filename: string,
): Promise<Buffer>;
/**
@ -72,8 +72,8 @@ export interface StorageService {
streamFile(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
filename: string
category: "uploads" | "generated" | "references",
filename: string,
): Promise<Readable>;
/**
@ -87,9 +87,9 @@ export interface StorageService {
getPresignedDownloadUrl(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
category: "uploads" | "generated" | "references",
filename: string,
expirySeconds: number
expirySeconds: number,
): Promise<string>;
/**
@ -104,10 +104,10 @@ export interface StorageService {
getPresignedUploadUrl(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
category: "uploads" | "generated" | "references",
filename: string,
expirySeconds: number,
contentType: string
contentType: string,
): Promise<string>;
/**
@ -120,8 +120,8 @@ export interface StorageService {
listFiles(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
prefix?: string
category: "uploads" | "generated" | "references",
prefix?: string,
): Promise<FileMetadata[]>;
/**
@ -134,8 +134,8 @@ export interface StorageService {
deleteFile(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
filename: string
category: "uploads" | "generated" | "references",
filename: string,
): Promise<void>;
/**
@ -148,7 +148,7 @@ export interface StorageService {
fileExists(
orgId: string,
projectId: string,
category: 'uploads' | 'generated' | 'references',
filename: string
category: "uploads" | "generated" | "references",
filename: string,
): Promise<boolean>;
}
}

31
apps/landing/README.md Normal file
View File

@ -0,0 +1,31 @@
# Banatie Landing Page
Next.js landing page for the Banatie AI image generation service.
## Features
- Landing page with service overview
- Demo page for image generation (TODO)
- Responsive design with Tailwind CSS
- Integration with Banatie API service
## Development
```bash
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm build
```
## TODO
- [ ] Implement image generation demo
- [ ] Add API integration with ../api-service
- [ ] Design landing page content
- [ ] Add pricing section
- [ ] Add contact form

View File

@ -0,0 +1,11 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ['localhost'],
},
}
module.exports = nextConfig

33
apps/landing/package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "@banatie/landing",
"version": "1.0.0",
"description": "Banatie Landing Page - Next.js landing page with image generation demo",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "next build",
"start": "next start -p 3001",
"lint": "next lint",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"typescript": "^5.9.2"
},
"devDependencies": {
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
}
}

View File

@ -0,0 +1,22 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Banatie - AI Image Generation',
description: 'Generate stunning images with AI using the Banatie service',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className="min-h-screen bg-gray-50">
<div className="container mx-auto px-4">
{children}
</div>
</body>
</html>
)
}

View File

@ -0,0 +1,28 @@
export default function HomePage() {
return (
<div className="py-12">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
Welcome to Banatie
</h1>
<p className="text-xl text-gray-600 mb-8">
AI-Powered Image Generation Service
</p>
<div className="bg-white rounded-lg shadow-md p-8 max-w-2xl mx-auto">
<h2 className="text-2xl font-semibold mb-4">Demo Coming Soon</h2>
<p className="text-gray-600">
Experience the power of AI image generation with our Gemini Flash model integration.
</p>
<div className="mt-6">
<button
className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50"
disabled
>
Try Demo (Coming Soon)
</button>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

16
apps/studio/.env.example Normal file
View File

@ -0,0 +1,16 @@
# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key
# Stripe Configuration
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# App Configuration
NEXTAUTH_URL=http://localhost:3002
NEXTAUTH_SECRET=your_nextauth_secret
# Banatie API
BANATIE_API_URL=http://localhost:3000

50
apps/studio/README.md Normal file
View File

@ -0,0 +1,50 @@
# Banatie Studio
SaaS platform for the Banatie AI image generation service. Based on Vercel's Next.js SaaS starter template with Supabase authentication and Stripe billing.
## Features
- 🔐 **Authentication**: Supabase Auth with social login
- 💳 **Billing**: Stripe subscriptions and payment processing
- 🎨 **AI Generation**: Integration with Banatie API service
- 📊 **Dashboard**: User dashboard with usage analytics
- 🏢 **Multi-tenant**: Organization and team management
## Tech Stack
- **Framework**: Next.js 14 with App Router
- **Authentication**: Supabase Auth
- **Database**: Supabase (PostgreSQL)
- **Payments**: Stripe
- **Styling**: Tailwind CSS
- **TypeScript**: Full type safety
## Development
```bash
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Start development server
pnpm dev
```
## Environment Setup
1. **Supabase**: Create a new project at [supabase.com](https://supabase.com)
2. **Stripe**: Set up your account at [stripe.com](https://stripe.com)
3. **Environment Variables**: Copy `.env.example` to `.env.local` and fill in your keys
## TODO
- [ ] Set up Supabase authentication
- [ ] Configure Stripe billing
- [ ] Implement user dashboard
- [ ] Add subscription tiers
- [ ] Integrate with Banatie API
- [ ] Add usage tracking
- [ ] Implement team management
- [ ] Add billing portal

View File

@ -0,0 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ['localhost', 'avatars.githubusercontent.com'],
},
env: {
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
},
}
module.exports = nextConfig

39
apps/studio/package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "@banatie/studio",
"version": "1.0.0",
"description": "Banatie Studio - SaaS platform for managing AI image generation subscriptions",
"private": true,
"scripts": {
"dev": "next dev -p 3002",
"build": "next build",
"start": "next start -p 3002",
"lint": "next lint",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"typescript": "^5.9.2",
"@supabase/supabase-js": "^2.45.0",
"@supabase/auth-helpers-nextjs": "^0.10.0",
"stripe": "^16.0.0",
"@stripe/stripe-js": "^4.0.0"
},
"devDependencies": {
"tailwindcss": "^3.4.0",
"autoprefixer": "^10.4.0",
"postcss": "^8.4.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.0",
"@tailwindcss/forms": "^0.5.0",
"@tailwindcss/typography": "^0.5.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
}
}

View File

@ -0,0 +1,22 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Banatie Studio - AI Image Generation SaaS',
description: 'Professional AI image generation platform with subscription management',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className="min-h-screen bg-gray-50">
<div className="min-h-screen">
{children}
</div>
</body>
</html>
)
}

View File

@ -0,0 +1,49 @@
export default function StudioPage() {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<div className="container mx-auto px-4 py-12">
<div className="text-center">
<h1 className="text-5xl font-bold text-gray-900 mb-6">
Banatie Studio
</h1>
<p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
Professional AI image generation platform with subscription management,
user authentication, and billing integration.
</p>
<div className="grid md:grid-cols-3 gap-8 mt-12 max-w-4xl mx-auto">
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-xl font-semibold mb-4">🔐 Authentication</h3>
<p className="text-gray-600">
Secure user authentication powered by Supabase with social login support.
</p>
</div>
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-xl font-semibold mb-4">💳 Billing</h3>
<p className="text-gray-600">
Stripe integration for subscription management and payment processing.
</p>
</div>
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="text-xl font-semibold mb-4">🎨 AI Generation</h3>
<p className="text-gray-600">
Professional image generation with usage tracking and limits.
</p>
</div>
</div>
<div className="mt-12">
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 max-w-2xl mx-auto">
<p className="text-yellow-800">
<strong>Coming Soon:</strong> This SaaS platform is under development.
Based on Vercel&apos;s Next.js SaaS starter template.
</p>
</div>
</div>
</div>
</div>
</div>
)
}

31
apps/studio/tsconfig.json Normal file
View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"],
"@/types/*": ["./src/types/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@ -2,14 +2,14 @@
services:
app:
build:
context: .
context: ./apps/api-service
target: development
container_name: banatie-app
ports:
- "3000:3000"
volumes:
- ./src:/app/src
- ./logs:/app/logs
- ./apps/api-service/src:/app/src
- ./apps/api-service/logs:/app/logs
networks:
- banatie-network
depends_on:

View File

@ -1,72 +1,55 @@
{
"name": "banatie",
"name": "banatie-monorepo",
"version": "1.0.0",
"description": "Nano Banana Image Generation Service - REST API for AI-powered image generation using Gemini Flash Image model",
"main": "dist/server.js",
"scripts": {
"dev": "tsx --watch src/server.ts",
"start": "node dist/server.js",
"build": "tsc",
"typecheck": "tsc --noEmit",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"format": "prettier --write src/**/*.ts",
"format:check": "prettier --check src/**/*.ts",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"clean": "rm -rf dist",
"prebuild": "npm run clean",
"prestart": "npm run build"
},
"keywords": [
"image-generation",
"ai",
"gemini",
"nano-banana",
"rest-api",
"express",
"typescript"
],
"author": "",
"license": "MIT",
"description": "Banatie AI Image Generation Service - Monorepo with API, Landing, Studio, and Admin apps",
"private": true,
"packageManager": "pnpm@10.11.0",
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"dependencies": {
"@google/genai": "^1.17.0",
"cors": "^2.8.5",
"dotenv": "^17.2.2",
"express": "^5.1.0",
"express-rate-limit": "^7.4.1",
"express-validator": "^7.2.0",
"helmet": "^8.0.0",
"mime": "3.0.0",
"minio": "^8.0.6",
"multer": "^2.0.2",
"winston": "^3.17.0"
"scripts": {
"dev": "pnpm --parallel run dev",
"dev:api": "pnpm --filter @banatie/api-service dev",
"dev:landing": "pnpm --filter @banatie/landing dev",
"dev:studio": "pnpm --filter @banatie/studio dev",
"dev:admin": "pnpm --filter @banatie/admin dev",
"build": "pnpm -r build",
"build:api": "pnpm --filter @banatie/api-service build",
"build:landing": "pnpm --filter @banatie/landing build",
"build:studio": "pnpm --filter @banatie/studio build",
"build:admin": "pnpm --filter @banatie/admin build",
"start:api": "pnpm --filter @banatie/api-service start",
"start:landing": "pnpm --filter @banatie/landing start",
"start:studio": "pnpm --filter @banatie/studio start",
"start:admin": "pnpm --filter @banatie/admin start",
"lint": "pnpm -r lint",
"lint:api": "pnpm --filter @banatie/api-service lint",
"lint:landing": "pnpm --filter @banatie/landing lint",
"lint:studio": "pnpm --filter @banatie/studio lint",
"lint:admin": "pnpm --filter @banatie/admin lint",
"typecheck": "pnpm -r typecheck",
"typecheck:api": "pnpm --filter @banatie/api-service typecheck",
"typecheck:landing": "pnpm --filter @banatie/landing typecheck",
"typecheck:studio": "pnpm --filter @banatie/studio typecheck",
"typecheck:admin": "pnpm --filter @banatie/admin typecheck",
"test": "pnpm --filter @banatie/api-service test",
"clean": "pnpm -r clean && rm -rf node_modules",
"install:all": "pnpm install"
},
"keywords": [
"monorepo",
"image-generation",
"ai",
"gemini",
"rest-api",
"nextjs",
"saas",
"admin-dashboard"
],
"author": "",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^9.36.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^29.5.14",
"@types/multer": "^2.0.0",
"@types/node": "^24.3.1",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^8.18.2",
"@typescript-eslint/parser": "^8.18.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"jest": "^29.7.0",
"nodemon": "^3.1.9",
"prettier": "^3.4.2",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- 'apps/*'