294 lines
17 KiB
Markdown
294 lines
17 KiB
Markdown
# 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 |
|