Files
indie/README.md
Krrish Ghimire 3e77b54d71 update readme
2026-07-08 23:25:29 +05:45

504 lines
23 KiB
Markdown

# Indie — Independent Website Platform
A platform that lets anyone create, own, and publish their own static website through AI prompts. Describe what you want, and the AI generates it. Every site is backed by a git repository.
---
## Quick Start
### Prerequisites
- Java 17+
- Node.js 18+ and npm
- Docker & Docker Compose (for PostgreSQL, MongoDB)
- npm packages: `@angular/cli@17` (`npm install -g @angular/cli@17`)
### Setup
1. **Start infrastructure:**
```bash
docker compose up -d
```
This starts PostgreSQL (port 5432) and MongoDB (port 27017).
2. **Start the backend:**
```bash
cd backend
./gradlew bootRun
```
The API will be available at `http://localhost:8080`.
Swagger UI at `http://localhost:8080/swagger-ui.html`.
3. **Start the frontend (separate terminal):**
```bash
cd frontend
npm install
ng serve
```
The app will be available at `http://localhost:4200`.
API calls are proxied to `http://localhost:8080`.
---
## Infrastructure (Docker Compose)
| Service | Image | Ports | Credentials | Volume |
|------------|--------------------|--------------|--------------------------------|---------------------|
| PostgreSQL | `postgres:16-alpine` | host:5432 | `indie` / `indie_dev` | `pgdata` |
| MongoDB | `mongo:7` | host:27017 | (no auth) | `mongodata` |
---
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| **Database** | | |
| `SPRING_DATASOURCE_URL` | PostgreSQL JDBC URL | `jdbc:postgresql://localhost:5432/indie` |
| `SPRING_DATASOURCE_USERNAME` | DB username | `indie` |
| `SPRING_DATASOURCE_PASSWORD` | DB password | `indie_dev` |
| `MONGODB_URI` | MongoDB connection URI | `mongodb://localhost:27017/indie` |
| **JWT** | | |
| `INDIE_JWT_ACCESS_EXPIRATION` | Access token TTL (ms) | `900000` (15 min) |
| `INDIE_JWT_REFRESH_EXPIRATION` | Refresh token TTL (ms) | `604800000` (7 days) |
| `INDIE_JWT_PRIVATE_KEY_PATH` | RS256 private key path | `config/jwt-private.key` |
| `INDIE_JWT_PUBLIC_KEY_PATH` | RS256 public key path | `config/jwt-public.key` |
| **Git** | | |
| `INDIE_GIT_REPOS_PATH` | Git repositories root | `/var/git/sites/` |
| `INDIE_GIT_AUTHOR_NAME` | Default git author name | `Indie Platform` |
| `INDIE_GIT_AUTHOR_EMAIL` | Default git author email | `git@indie.local` |
| **Encryption** | | |
| `INDIE_ENCRYPTION_KEY_PATH` | AES-256-GCM master key path | `config/encryption.key` |
| **App** | | |
| `INDIE_APP_BASE_URL` | Frontend base URL (for verification emails) | `http://localhost:4200` |
| `INDIE_VERIFICATION_TOKEN_EXPIRATION` | Email verification token TTL (minutes) | `1440` (24h) |
| `INDIE_CORS_ORIGINS` | Allowed CORS origins (comma-separated) | `http://localhost:4200,http://localhost:3000` |
| **LLM** | | |
| `GEMINI_API_KEY` | Google Gemini API key | — |
| `GEMINI_MODEL` | Gemini model name | `gemini-2.0-flash` |
| `OPENAI_API_KEY` | OpenAI API key | — |
| `OPENAI_MODEL` | OpenAI model name | `gpt-4o-mini` |
| `INDIE_LLM_RATE_LIMIT` | Max LLM requests per hour per user | `10` |
| **Email (MailHog compatible)** | | |
| `EMAIL_HOST` | SMTP host | `localhost` |
| `EMAIL_PORT` | SMTP port | `1025` |
| `EMAIL_USERNAME` | SMTP username | _(empty)_ |
| `EMAIL_PASSWORD` | SMTP password | _(empty)_ |
| `EMAIL_SMTP_AUTH` | Enable SMTP auth | `true` |
| `EMAIL_SMTP_STARTTLS` | Enable STARTTLS | `true` |
---
## Project Structure
```
backend/
├── build.gradle # Dependencies and build config
├── settings.gradle # Project name
├── gradlew / gradlew.bat # Gradle wrapper
├── gradle/wrapper/ # Wrapper JAR + properties
└── src/
├── main/java/com/krrishg/
│ ├── IndieApplication.java # Spring Boot main class (@EnableScheduling)
│ ├── config/
│ │ ├── SecurityConfig.java # HTTP security, JWT filter, CORS
│ │ ├── JwtConfig.java # RS256 token generation and validation
│ │ ├── TokenService.java # Interface for token generation/validation
│ │ ├── JwtAuthenticationToken.java # Custom AbstractAuthenticationToken
│ │ ├── UserPrincipal.java # UserDetails record
│ │ ├── GitConfig.java # Git repository settings
│ │ ├── EncryptionService.java # AES-256/GCM for LLM API key encryption
│ │ ├── RateLimitConfig.java # Bucket4j token-bucket rate limiter
│ │ ├── SchemaMigration.java # Startup DDL migrations
│ │ ├── MapToJsonConverter.java # JPA converter for JSON text columns
│ │ └── GlobalExceptionHandler.java # @ControllerAdvice error handling
│ ├── model/
│ │ ├── User.java # User entity (email, password, emailVerified)
│ │ ├── VerificationToken.java # Email verification / set-password tokens
│ │ ├── Site.java # Site entity per user
│ │ ├── SiteStatus.java # Enum: DRAFT, PUBLISHED
│ │ ├── AuthProvider.java # Enum: LOCAL only (OAuth planned)
│ │ ├── GitRemote.java # Connected git remote (GitHub/GitLab)
│ │ ├── SelfHostedConfig.java # SSH/deploy configuration per site
│ │ └── GenerationVersion.java # MongoDB: LLM generation history per site
│ ├── repository/
│ │ ├── UserRepository.java
│ │ ├── SiteRepository.java
│ │ ├── GitRemoteRepository.java
│ │ ├── SelfHostedConfigRepository.java
│ │ └── VerificationTokenRepository.java
│ ├── dto/
│ │ ├── RegisterRequest.java
│ │ ├── RegistrationResponse.java
│ │ ├── LoginRequest.java
│ │ ├── RefreshTokenRequest.java
│ │ ├── AuthResponse.java # JWT tokens + user info
│ │ ├── VerifyEmailRequest.java
│ │ ├── VerifyEmailResponse.java
│ │ ├── ResendVerificationRequest.java
│ │ ├── SetPasswordRequest.java
│ │ ├── SiteRequest.java # Includes deploymentTarget field
│ │ ├── SiteResponse.java
│ │ ├── SiteStructure.java # Top-level DTO: pages + theme
│ │ ├── Reference.java # URL/IMAGE reference for LLM context
│ │ ├── LLMOptions.java # LLM generation options
│ │ ├── LLMResult.java # LLM generation result
│ │ ├── LLMConfigRequest.java # User LLM provider config
│ │ ├── SelfHostedConfigRequest.java
│ │ └── SelfHostedConfigResponse.java
│ ├── service/
│ │ ├── AuthService.java # Register, login, token refresh, verify email
│ │ ├── EmailService.java # Verification email sender
│ │ ├── CleanupService.java # Scheduled cleanup of stale unverified accounts
│ │ ├── LLMService.java # Orchestrates LLM calls, stores/restores versions
│ │ ├── LLMResponseParser.java # Parses LLM JSON into SiteStructure
│ │ ├── ReferenceService.java # Fetches web page content for LLM context
│ │ ├── RenderService.java # Renders SiteStructure into HTML + CSS + JS
│ │ ├── DeploymentService.java # Orchestrates publish/unpublish
│ │ ├── Deployer.java # Interface for self-hosted deployment
│ │ ├── SshRsyncDeployer.java # SSH + rsync + nginx + Let's Encrypt
│ │ ├── PagesProvider.java # Interface for git provider Pages
│ │ ├── GitHubPagesProvider.java # GitHub Pages implementation
│ │ ├── GitLabPagesProvider.java # GitLab Pages implementation
│ │ ├── GitService.java # JGit: init, commit, push, log, rollback
│ │ ├── KeyManager.java # SSH key-pair generation per site
│ │ ├── ExportService.java # Exports site structure to ZIP
│ │ └── DomainService.java # Custom domain resolution skeleton
│ ├── service/llm/
│ │ ├── LLMProvider.java # Interface for LLM providers
│ │ ├── GeminiProvider.java # Google Gemini implementation
│ │ ├── OpenAIProvider.java # OpenAI implementation
│ │ └── ProviderFactory.java # Factory creating providers by name + key
│ ├── engine/
│ │ ├── JsBundle.java # Generates static JS (nav, smooth scroll, lightbox, contact form)
│ │ └── StyleCompiler.java # Generates base CSS with custom property fallbacks
│ └── controller/
│ ├── AuthController.java # POST /api/auth/*
│ ├── SiteController.java # CRUD /api/sites
│ ├── LLMController.java # POST /api/llm/generate, /refine, /versions/*
│ ├── GitController.java # POST /api/sites/{id}/git/*
│ ├── DeploymentController.java # POST /api/sites/{id}/publish, /unpublish
│ ├── ExportController.java # GET /api/sites/{id}/export
│ ├── UserSettingsController.java # GET/PUT/DELETE /api/user/llm-config
│ └── SelfHostedController.java # CRUD /api/sites/{id}/self-hosted
├── main/resources/
│ ├── application.yml # All configuration
│ └── prompts/
│ ├── site-generator.txt # LLM system prompt for site generation
│ └── site-refiner.txt # LLM system prompt for refinement
└── test/java/com/krrishg/
├── config/ # 7 config tests
├── controller/ # 9 controller tests
├── dto/ # 7 DTO tests
├── engine/ # 2 engine tests
├── model/ # 7 model tests
├── repository/ # 4 repository tests
├── service/ # 16 service tests + fakes
├── service/llm/ # 3 LLM provider tests
└── support/ # 13 test fakes, stubs, and configs
frontend/
├── Dockerfile # Multi-stage production build
├── nginx.conf # Nginx config (SPA fallback + /api/ proxy)
├── proxy.conf.json # Dev proxy to backend
├── angular.json # Angular CLI configuration
├── tailwind.config.js # Tailwind CSS configuration
├── postcss.config.js # PostCSS (tailwindcss + autoprefixer)
├── package.json
├── tsconfig.json / tsconfig.app.json / tsconfig.spec.json
├── src/
│ ├── index.html
│ ├── main.ts # Angular bootstrap
│ ├── styles.css # Global styles + Tailwind imports
│ ├── favicon.ico
│ ├── assets/
│ └── app/
│ ├── app.component.ts / .html / .css
│ ├── app.config.ts # App providers
│ ├── app.routes.ts # Lazy-loaded routes
│ ├── core/
│ │ ├── models/
│ │ │ ├── auth-response.ts # AuthResponse & UserInfo
│ │ │ ├── site.ts # SiteResponse
│ │ │ ├── page.ts # PageResponse
│ │ │ ├── llm.ts # LLM request/response types, GenerationVersion
│ │ │ └── deployment.ts # Git/self-hosted config types
│ │ ├── services/
│ │ │ ├── auth.service.ts # Auth HTTP + token storage
│ │ │ ├── site.service.ts # Site CRUD HTTP
│ │ │ ├── page.service.ts # Page CRUD HTTP
│ │ │ ├── deployment.service.ts # Export, git, publish HTTP
│ │ │ ├── llm-api.service.ts # LLM generate/refine/version HTTP
│ │ │ └── llm-config.service.ts # User LLM key storage HTTP
│ │ ├── guards/
│ │ │ ├── auth.guard.ts # Redirects unauthenticated to /login
│ │ │ └── login.guard.ts # Redirects authenticated to /dashboard
│ │ └── interceptors/
│ │ └── jwt.interceptor.ts # Attaches Bearer token
│ ├── auth/
│ │ ├── login/
│ │ │ └── login.component.ts
│ │ ├── register/
│ │ │ └── register.component.ts
│ │ └── verify-email/
│ │ └── verify-email.component.ts # Email verification + set-password
│ ├── dashboard/
│ │ ├── dashboard.component.ts # Site cards grid + toolbar
│ │ ├── create-site-dialog/
│ │ │ └── create-site-dialog.ts
│ │ └── delete-site-dialog/
│ │ └── delete-site-dialog.ts
│ └── llm-workspace/
│ ├── llm-workspace.component.ts # Main workspace (prompt, preview, timeline)
│ ├── llm-session.service.ts # LLM generation session state
│ ├── settings/
│ │ └── settings.component.ts # LLM provider key/dialog settings
│ ├── git-settings/
│ │ └── git-settings.component.ts # Git remote connection settings
│ ├── preview-dialog/
│ │ └── preview-dialog.component.ts # Preview generated site
│ └── delete-version-dialog/
│ └── delete-version-dialog.ts # Confirm delete version
config/ # Auto-generated on first boot
├── jwt-private.key # RS256 private key
├── jwt-public.key # RS256 public key
└── encryption.key # AES-256-GCM master key (base64)
```
---
## API Overview
### Authentication (`/api/auth`)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/auth/register` | Register with name + email + password (returns verification pending) |
| POST | `/api/auth/login` | Login with email + password |
| POST | `/api/auth/refresh` | Refresh access token |
| POST | `/api/auth/logout` | Logout |
| POST | `/api/auth/verify-email` | Verify email via token, returns setup token |
| POST | `/api/auth/resend-verification` | Resend verification email |
| POST | `/api/auth/set-password` | Set password after email verification |
### Sites (`/api/sites`)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/sites` | List user's sites |
| POST | `/api/sites` | Create a new site (with `deploymentTarget`: `GIT_PAGES`, `SELF_HOSTED`, or `NONE`) |
| GET | `/api/sites/{id}` | Get site details |
| PUT | `/api/sites/{id}` | Update site |
| DELETE | `/api/sites/{id}` | Delete site |
### LLM Generation (`/api/llm`)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/llm/generate` | Generate site from prompt (rate-limited to 10/hour/user) |
| POST | `/api/llm/refine` | Refine existing site structure with follow-up prompt |
| GET | `/api/llm/versions/{siteId}` | List generation versions for a site |
| POST | `/api/llm/versions/{id}/restore` | Restore a specific generation version |
| DELETE | `/api/llm/versions/{id}` | Delete a generation version |
### Git Remotes (`/api/sites/{id}/git`)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/sites/{id}/git/connect` | Connect git remote (GitHub/GitLab) |
| POST | `/api/sites/{id}/git/disconnect` | Disconnect git remote |
| GET | `/api/sites/{id}/git/status` | Get connection status, SSH keys, commit log |
### Deployment (`/api/sites/{id}`)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/sites/{id}/publish` | Publish site (git push) |
| POST | `/api/sites/{id}/unpublish` | Unpublish site |
| GET | `/api/sites/{id}/export` | Download site as `.zip` archive |
### Self-Hosted Deployment (`/api/sites/{id}/self-hosted`)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/sites/{id}/self-hosted` | Get SSH/deploy configuration |
| PUT | `/api/sites/{id}/self-hosted` | Save SSH/deploy configuration |
| DELETE | `/api/sites/{id}/self-hosted` | Remove configuration |
### User Settings (`/api/user`)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/user/llm-config` | Get saved LLM provider configs (keys redacted) |
| PUT | `/api/user/llm-config` | Save API key / base URL / model for a provider |
| DELETE | `/api/user/llm-config/{provider}` | Delete saved config for a provider |
---
## Frontend Routes
| Path | Component | Auth |
|------|-----------|------|
| `/login` | LoginComponent | Redirects to /dashboard if already logged in |
| `/register` | RegisterComponent | Redirects to /dashboard if already logged in |
| `/verify-email` | VerifyEmailComponent | Public (email verification flow) |
| `/dashboard` | DashboardComponent | Requires auth |
| `/llm/:siteId` | LlmWorkspaceComponent | Requires auth |
| `/llm/:siteId/settings` | SettingsComponent | Requires auth |
| `/llm/:siteId/git` | GitSettingsComponent | Requires auth |
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Backend | Java 17, Spring Boot 3.2, Gradle (Groovy) |
| Frontend | Angular 17, Angular Material, Tailwind CSS, Leaflet |
| Relational DB | PostgreSQL 16 |
| Document DB | MongoDB 7 (generation versions, LLM sessions) |
| Auth | Spring Security + JWT (RS256), email/password |
| LLM | Gemini (primary), OpenAI (fallback), provider-abstraction |
| Git | JGit — local repo per site, GitHub/GitLab remote push |
| Email | Spring Boot Mail (MailHog compatible) |
| Rate Limiting | Bucket4j |
| API Docs | Springdoc OpenAPI (Swagger UI) |
---
## Architecture
```
┌──────────────────────┐
│ Spring Boot API │
│ (VPS / Docker) │
└────────┬─────────────┘
┌──────────────┼──────────────┐
│ │ │
┌────────▼───┐ ┌──────▼──────┐ ┌───▼────────┐
│ PostgreSQL │ │ MongoDB │ │ Git Repos │
│ (users, │ │ (versions, │ │ /var/git/ │
│ sites) │ │ sessions) │ └────────────┘
└─────────────┘ └─────────────┘
```
---
## Development
```bash
# Backend
cd backend
./gradlew build # Build
./gradlew test # Run tests (unit + slice + integration)
./gradlew bootRun # Run locally (requires docker compose up -d first)
# Frontend
cd frontend
npm install # Install dependencies
ng serve # Dev server at localhost:4200
ng build # Production build (outputs to dist/)
ng test # Run unit tests (Jasmine + Karma)
```
### Production Build (Docker)
```bash
docker build -t indie-frontend frontend/
docker run -p 80:80 indie-frontend
```
The frontend Docker image serves the app via nginx (port 80) and proxies `/api/` requests to `http://backend:8080`.
---
## Roadmap
### v0.1 — Auth & Foundation ✅
- [x] User registration (email/password + email verification)
- [x] User login (email/password + JWT)
- [x] Token refresh with 7-day refresh token
- [x] Site CRUD (create, list, get, update, delete)
- [x] Frontend: Login, Register, Verify Email, Dashboard
- [x] Frontend: JWT interceptor, auth guard, login guard, lazy routes
- [x] Scheduled cleanup of unverified accounts
### v0.2 — LLM Prompt Workspace ✅
- [x] Prompt-to-site generation via Gemini/OpenAI
- [x] Version timeline with MongoDB storage
- [x] Live preview (sandboxed iframe)
- [x] Content editor (grouped by page)
- [x] Refine via follow-up prompts
- [x] Rate limiting (10 generations/hour/user)
- [x] Reference URL support for context
### v0.3 — Export, Git & Hosting ✅
- [x] Render engine (HTML + CSS + JS generation)
- [x] Git init, commit, push via JGit
- [x] SSH key-pair management per site
- [x] GitHub Pages / GitLab Pages providers
- [x] Export as `.zip` archive
- [x] Publish/unpublish flow
- [x] Custom domain skeleton
- [x] Self-hosted deployment (SSH + rsync + nginx)
---
## LLM Provider Abstraction
```java
public interface LLMProvider {
LLMResult generate(String systemPrompt, String userPrompt,
List<Reference> references, LLMOptions options);
String getName();
}
```
Adding a new provider = implement `LLMProvider` interface + add config block in `application.yml`.
Provider API keys can be configured globally via env vars (`GEMINI_API_KEY`, `OPENAI_API_KEY`) or per-user through the `/api/user/llm-config` endpoint (encrypted at rest via AES-256-GCM).
---
## Key Design Decisions
1. **Pure prompt-based** — No drag-and-drop builder. Every site starts from a prompt. Iteration happens through follow-up prompts and inline text edits.
2. **Static output only** — Generated sites are pure HTML/CSS/JS. No runtime framework dependency. Users own the output forever.
3. **Git as source of truth** — Every site is a git repository. Publish = commit. Rollback = checkout. Export includes full `.git/`.
4. **Version timeline** — Every generation creates a version in MongoDB. Users can browse, preview, and restore any previous version.
5. **No billing in MVP** — Monetization is post-MVP.
---
## config/ Directory
The following files live in `config/` and are auto-generated on first boot if they don't exist.
In production, generate and mount them manually:
```bash
# RS256 key pair for JWT
openssl genpkey -algorithm RSA -out config/jwt-private.key -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in config/jwt-private.key -out config/jwt-public.key
# AES-256-GCM master key for LLM API key encryption
openssl rand -base64 32 > config/encryption.key
```
Set the `INDIE_JWT_PRIVATE_KEY_PATH`, `INDIE_JWT_PUBLIC_KEY_PATH`, and
`INDIE_ENCRYPTION_KEY_PATH` env vars to point to these files.