Files
indie/PLAN.md
2026-06-30 11:13:55 +05:45

294 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Indie — Independent Website Platform
A platform that lets anyone create, own, and publish their own static website without social media gatekeeping. Built with a drag-and-drop builder and LLM prompt-to-site generation. Every site is backed by a git repository. Users self-host or use free subdomain hosting.
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Backend | Java 17, Spring Boot 3.x, Gradle (Groovy) |
| Frontend | Angular 17, Angular Material, Angular CDK (drag-drop) |
| Relational DB | PostgreSQL |
| Document DB | MongoDB (LLM sessions) |
| Object Storage | Cloudflare R2 (S3-compatible) |
| LLM | Gemini (primary), OpenAI (fallback), provider-abstraction for future |
| Auth | Spring Security + OAuth2 Client — email/password, Google OAuth, GitHub OAuth, OpenID Connect |
| Git | JGit — local repo per site, optional GitHub/GitLab remote push |
| Infra | Docker Compose (dev), R2 + Cloudflare (hosting), Caddy/nginx (prod reverse proxy) |
---
## Build Phases
### v0.1 — Auth & Foundation (Target: Weeks 1-3)
**Goal:** User can register, log in via email or OAuth, and create/manage sites and pages.
#### Backend
| File | Action | Details |
|------|--------|---------|
| `backend/build.gradle` | Add | Spring Boot 3.x, deps: web, jpa, security, oauth2-client, oauth2-resource-server, validation, lombok, postgresql, testcontainers, aws-java-sdk-s3 (R2), springdoc-openapi, jgit |
| `backend/settings.gradle` | Add | Project name, plugin management |
| `backend/src/main/resources/application.yml` | Add | DB configs, JWT keys, OAuth client IDs/secrets, LLM keys, R2 credentials, git config |
| `com/indie/IndieApplication.java` | Add | Main class |
| `com/indie/config/SecurityConfig.java` | Add | SecurityFilterChain with JWT filter + OAuth2 login + OpenID Connect. Route permissions: `/api/auth/**` public, `/api/**` authenticated |
| `com/indie/config/JwtConfig.java` | Add | RS256 signing, access token 15min, refresh token 7d |
| `com/indie/config/GitConfig.java` | Add | Repos root path (`/var/git/sites/`), author name/email |
| `com/indie/model/User.java` | Add | id(UUID), email, password(bcrypt, nullable), name, avatarUrl, provider(LOCAL/GOOGLE/GITHUB/OPENID), providerId, emailVerified, createdAt, updatedAt |
| `com/indie/model/Site.java` | Add | id, userId, name, description, subdomain(unique), themeConfig(JSONB), status(DRAFT/PUBLISHED), publishedAt, createdAt, updatedAt |
| `com/indie/model/Page.java` | Add | id, siteId(FK), title, slug, seoTitle, seoDescription, orderIndex, createdAt |
| `com/indie/model/Component.java` | Add | id, pageId(FK), type(enum), config(JSONB), styles(JSONB), orderIndex |
| `com/indie/repository/*.java` | Add | JPA repositories for User, Site, Page, Component |
| `com/indie/service/AuthService.java` | Add | register, login, refreshToken, oauthLogin |
| `com/indie/service/OAuth2UserService.java` | Add | Maps Google/GitHub/OpenID user info to User entity |
| `com/indie/controller/AuthController.java` | Add | `POST /api/auth/register`, `/login`, `/refresh`, `/logout` |
| `com/indie/controller/SiteController.java` | Add | Full CRUD: `GET/POST /api/sites`, `GET/PUT/DELETE /api/sites/{id}` |
| `com/indie/controller/PageController.java` | Add | CRUD for pages within a site |
#### Frontend
| File | Action | Details |
|------|--------|---------|
| `frontend/` | Init | Angular 17 standalone, Angular Material, lazy routes, Tailwind CSS |
| `src/app/auth/` | Add | LoginComponent, RegisterComponent, OAuth callback handler, AuthService, AuthGuard, JWT interceptor |
| `src/app/dashboard/` | Add | DashboardComponent (list site cards), CreateSiteDialog, DeleteSiteDialog |
#### Infrastructure
| File | Action | Details |
|------|--------|---------|
| `docker-compose.yml` | Add | PostgreSQL, MongoDB, MinIO (local R2 mock), backend (8080), frontend (4200) |
---
### v0.2 — Drag-and-Drop Builder (Target: Weeks 4-6)
**Goal:** User can visually build a website by dragging components onto a canvas, editing their properties, styling via theme editor, and previewing live.
#### Backend
| File | Action | Details |
|------|--------|---------|
| `com/indie/controller/BuilderController.java` | Add | `GET /api/sites/{id}/pages` (with nested components), `PUT /api/sites/{id}/pages/{pageId}/components` (batch save), `GET/PUT /api/sites/{id}/theme` |
| `com/indie/service/BuilderService.java` | Add | Validate component tree (type enum, config schema, max 50/page), save in transaction |
#### Frontend
| File | Action | Details |
|------|--------|---------|
| `src/app/builder/builder.component.ts` | Add | 3-panel layout (palette 250px, canvas flex, properties 320px) via CSS Grid or mat-sidenav |
| `src/app/builder/services/builder-state.service.ts` | Add | BehaviorSubject store: `components$`, `selectedComponentId$`, `themeConfig$`, `pages$`. Methods: add, remove, update, reorder. Undo/redo stack (50 snapshots) |
| `src/app/builder/services/auto-save.service.ts` | Add | 2s debounce → `PUT` batch save. "Saving..."/"Saved" indicator |
| `src/app/builder/component-palette/` | Add | PaletteComponent — Material list of 10 draggable component types, organized by category |
| `src/app/builder/canvas/` | Add | CanvasComponent — `cdkDropList` accepting drops, renders components in order, highlight on select, delete on hover |
| `src/app/builder/property-panel/` | Add | PropertyPanelComponent — dynamic form per type (config + styles sections). Material expansion panels |
| `src/app/builder/renderer/` | Add | Render components per type (heading, text, image, button, divider, gallery, video, contact_form, map, custom_html) — each takes `config` + `styles` as `@Input()` |
| `src/app/builder/theme-editor/` | Add | ThemeEditorComponent — color pickers, font selectors, spacing slider. Live preview updates |
| `src/app/builder/preview/` | Add | PreviewComponent — sandboxed `<iframe>` with `srcdoc`, responsive device toggles (desktop/tablet/mobile) |
| `src/app/builder/builder-api.service.ts` | Add | HTTP calls for save/load components, theme, pages |
**Component types:** heading, text, image, button, divider, gallery, video, contact_form, map, custom_html
---
### v0.3 — LLM Prompt-to-Site (Target: Weeks 7-8)
**Goal:** User types a description of their desired website → AI generates the full site structure (pages + components + theme) → user tweaks in builder or refines via another prompt.
#### Backend
| File | Action | Details |
|------|--------|---------|
| `com/indie/service/llm/LLMProvider.java` | Add | Interface: `generate(systemPrompt, userPrompt, options) → LLMResult`. `LLMResult`: content, tokenUsage, model, latency |
| `com/indie/service/llm/GeminiProvider.java` | Add | Implements LLMProvider via Gemini API. Config: `indie.llm.providers.gemini.api-key`, model |
| `com/indie/service/llm/OpenAIProvider.java` | Add | Implements LLMProvider via OpenAI API. Config: `indie.llm.providers.openai.api-key`, model |
| `com/indie/service/llm/LLMFactory.java` | Add | Returns configured provider from `indie.llm.active-provider`, fallback chain |
| `com/indie/dto/LLMOptions.java` | Add | temperature, maxTokens, timeout, responseFormat |
| `com/indie/dto/SiteStructure.java` | Add | Top-level DTO: `List<PageStructure> pages`, `ThemeConfig theme`. Nested types for page and component |
| `com/indie/dto/LLMResult.java` | Add | content(String), tokenUsage, model, latencyMs |
| `com/indie/model/LLMSession.java` | Add | MongoDB document: userId, prompt, generatedStructure, acceptedAt, refinedPrompt, createdAt |
| `com/indie/service/LLMService.java` | Add | Orchestrator: prompt → LLM → parse → validate → return SiteStructure |
| `com/indie/service/LLMResponseParser.java` | Add | Parse LLM JSON output, handle markdown fences, malformed fields, partial extraction |
| `com/indie/controller/LLMController.java` | Add | `POST /api/llm/generate` (prompt→structure), `POST /api/llm/refine` (structure+prompt→refined). Rate limit: 10/hour/user |
| `com/indie/config/RateLimitConfig.java` | Add | Rate limiting via bucket4j |
| `src/main/resources/prompts/site-generator.txt` | Add | System prompt: schema definition + 2-3 few-shot examples |
#### Frontend
| File | Action | Details |
|------|--------|---------|
| `src/app/llm-workspace/` | Add | LLMWorkspaceComponent — textarea, "Generate" button, loading spinner with ETA, result preview summary, "Apply to Builder" button |
| `src/app/llm-workspace/llm-session.service.ts` | Add | Prompt history, refinement flow (original prompt + user edits + refinement → new generation) |
---
### v0.4 — Export, Git & Hosting (Target: Weeks 9-11)
**Goal:** User can publish their site as pure static files. Every publish creates a git commit. User can connect GitHub/GitLab for automatic pushes. Sites served at `*.indie.com` for free.
#### Backend
| File | Action | Details |
|------|--------|---------|
| `com/indie/service/RenderService.java` | Add | Core render engine: `renderSite(site)`, `renderPage(site, page, components) → String`, `renderCss(theme, components) → String`, `renderJs() → String` |
| `com/indie/engine/templates/*.html` | Add | Thymeleaf templates per component type + `layout.html` + `head.html` |
| `com/indie/engine/StyleCompiler.java` | Add | Theme → CSS custom properties at `:root`, component styles → CSS classes, responsive breakpoints |
| `com/indie/engine/JsBundle.java` | Add | Generates `script.js`: nav toggle, smooth scroll, lightbox, contact form handler, comment form handler. Minified |
| `com/indie/service/ExportService.java` | Add | `export(siteId) → ZipOutputStream` — full site directory + `.git/` |
| `com/indie/controller/ExportController.java` | Add | `GET /api/sites/{id}/export``application/zip` download |
| `com/indie/service/GitService.java` | Add | `initRepo(siteId)`, `commit(siteId, message)`, `commitAndPush(siteId, message)`, `getLog(siteId)`, `rollback(siteId, commitHash)`, `exportRepo(siteId)` |
| `com/indie/model/GitRemote.java` | Add | JPA: id, siteId, provider(GITHUB/GITLAB), remoteUrl, encryptedToken, defaultBranch |
| `com/indie/service/KeyManager.java` | Add | SSH keypair generation per site, encrypted storage |
| `com/indie/controller/GitController.java` | Add | `POST /api/sites/{id}/git/connect`, `/disconnect`, `GET /api/sites/{id}/git/status` |
| `com/indie/service/DeploymentService.java` | Add | On publish: render → write to git working dir → `GitService.commit` → upload to R2 → return public URL |
| `com/indie/controller/DeploymentController.java` | Add | `POST /api/sites/{id}/publish`, `/unpublish` |
| `com/indie/service/DomainService.java` | Add | Skeleton: store custom domain mapping in Site entity, prepared for future TLS |
| `com/indie/service/MediaService.java` | Add | Upload to R2, validate type/size, resize |
| `com/indie/controller/MediaController.java` | Add | `POST /api/media/upload`, `GET /api/media/{id}` |
#### Frontend
| File | Action | Details |
|------|--------|---------|
| `src/app/builder/git-settings/` | Add | GitSettingsComponent — connect/disconnect GitHub/GitLab OAuth, select remote repo, connection status |
| `src/app/builder/settings/` | Add | SettingsComponent — site name, subdomain, export zip button, publish/unpublish buttons |
#### Infrastructure
| File | Action | Details |
|------|--------|---------|
| `infra/cloudflare-worker.js` | Add | Routes `*.indie.com` → R2 bucket `indie-sites/{subdomain}/` |
| `docker-compose.prod.yml` | Add | Production services: PostgreSQL, MongoDB, backend (×2), nginx, certbot |
**Git directory structure on server:**
```
/var/git/sites/
└── {siteId}/
├── .git/
├── index.html
├── about/index.html
├── style.css
├── script.js
└── assets/
```
---
### v0.5 — Social Features & Polish (Target: Weeks 12-13)
**Goal:** Minimal social features (comments, RSS) and final polish before public release.
#### Backend
| File | Action | Details |
|------|--------|---------|
| `com/indie/model/Comment.java` | Add | JPA: id(UUID), siteId, pageId, authorName, authorEmail, content, isApproved, parentCommentId, createdAt |
| `com/indie/controller/CommentController.java` | Add | Public: `POST /api/sites/{subdomain}/comments`. Authenticated: `GET/PUT/DELETE` moderation |
| `com/indie/service/RssService.java` | Add | `generateRss(siteId) → String` — RSS 2.0 feed from site pages |
| `com/indie/controller/RssController.java` | Add | `GET /api/feed/{subdomain}``application/rss+xml`, 1h cache |
#### Frontend
| File | Action | Details |
|------|--------|---------|
| `src/app/builder/settings/` | Modify | Add git history panel (commit list, rollback button, download repo) |
| `src/app/builder/components/comments-section/` | Add | In-builder config (enable/disable, moderate) + renderer for generated sites |
---
## Infrastructure Architecture
```
┌──────────────────────┐
│ Cloudflare CDN │
│ *.indie.com → R2 │
└────────┬─────────────┘
┌────────▼─────────────┐
│ Cloudflare R2 │
│ indie-sites bucket │
│ /{subdomain}/* │
└──────────────────────┘
┌────────┴─────────────┐
│ Spring Boot API │
│ (VPS / Docker) │
└────────┬─────────────┘
┌──────────────┼──────────────┐
│ │ │
┌────────▼───┐ ┌──────▼──────┐ ┌───▼────────┐
│ PostgreSQL │ │ MongoDB │ │ Git Repos │
│ (users, │ │ (LLM sess.) │ │ /var/git/ │
│ sites, │ └─────────────┘ └────────────┘
│ pages, │
│ comps) │
└─────────────┘
```
---
## LLM Provider Abstraction
```java
// Adding a new provider = implement this interface + add config
public interface LLMProvider {
LLMResult generate(String systemPrompt, String userPrompt, LLMOptions options);
String getName();
}
// application.yml
indie:
llm:
active-provider: gemini
fallback-provider: openai
providers:
gemini:
api-key: ${GEMINI_API_KEY}
model: gemini-2.0-flash
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-4o-mini
```
---
## Key Design Decisions
1. **Static output only** — Generated sites are pure HTML/CSS/JS. No Angular/React dependency for end users. They own the output forever, no lock-in.
2. **Git as source of truth** — Every site is a git repository. Publish = commit. Rollback is a `git checkout`. Export includes full `.git/` history.
3. **Shared rendering** — The same component renderers drive the builder preview AND the static export. What you see is what you get.
4. **No billing in MVP** — Subdomain hosting is free. No Stripe/PayPal code. Monetization (paid hosting, custom domains) is a post-MVP concern.
5. **Incremental releases** — Each v0.x is independently shippable. If timeline slips, ship what's done.
---
## Validation Strategy
| Release | What to Test |
|---------|-------------|
| v0.1 | Register, login (email + OAuth), refresh token, site CRUD, page CRUD, 401 on unauthenticated |
| v0.2 | Drag → reorder → save → reload matches, theme change reflects in preview, undo/redo, all 10 component types render |
| v0.3 | LLM generates valid SiteStructure JSON, generation loads into builder, refinement updates correctly, error handling (timeout, bad JSON) |
| v0.4 | Exported zip opens in browser identically to preview, git init + commit works, git push to remote works, R2 upload succeeds, subdomain serves content |
| v0.5 | Comment submit → approve → display, RSS validates, image upload + resize, rollback restores previous version |
---
## Risk Register
| Risk | Likelihood | Mitigation |
|------|-----------|------------|
| Scope creep (adding features mid-phase) | High | Strict feature freeze per v0.x; ship as-is |
| Drag-drop performance with many components | Medium | Cap at 50 components/page; virtual scroll if needed |
| LLM output quality inconsistent | Medium | Iterate on system prompt; template gallery fallback |
| Solo developer burnout | Medium | Realistic timeline; celebrate each release |
| LLM API costs | Low | Gemini-2.0-flash is free-tier friendly; rate limit 10/hour/user |