335 lines
18 KiB
Markdown
335 lines
18 KiB
Markdown
# Indie — Independent Website Platform
|
||
|
||
A platform that lets anyone create, own, and publish their own static website through AI prompts. No drag-and-drop builder — just describe what you want, and the AI generates it. Every site is backed by a git repository.
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
| Layer | Technology |
|
||
|-------|-----------|
|
||
| Backend | Java 17, Spring Boot 3.x, Gradle (Groovy) |
|
||
| Frontend | Angular 17, Angular Material |
|
||
| Relational DB | PostgreSQL |
|
||
| Document DB | MongoDB (generation versions, 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), nginx (prod reverse proxy) |
|
||
|
||
---
|
||
|
||
## Core Interaction Model
|
||
|
||
```
|
||
Prompt + Reference URLs
|
||
│
|
||
▼
|
||
┌──────────────────┐ ┌─────────────────┐
|
||
│ LLM generates │────▶│ Version saved │
|
||
│ site structure │ │ to timeline │
|
||
└──────────────────┘ └─────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌──────────────────────────────────────────┐
|
||
│ Workspace Layout: │
|
||
│ ┌──────────┬──────────────────────────┐ │
|
||
│ │ Version │ Live Preview │ │
|
||
│ │ Timeline │ (sandboxed iframe) │ │
|
||
│ │ sidebar │ │ │
|
||
│ └──────────┴──────────────────────────┘ │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ Content Editor (grouped by page) │ │
|
||
│ │ ▼ Homepage │ │
|
||
│ │ Heading: [editable text] │ │
|
||
│ │ Body: [editable text] │ │
|
||
│ │ ▼ About (collapsible) │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
└──────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
Refine via new prompt or inline text edits
|
||
│
|
||
▼
|
||
Publish → Export → Git → Hosting
|
||
```
|
||
|
||
---
|
||
|
||
## Build Phases
|
||
|
||
### v0.1 — Auth & Foundation (Target: Weeks 1-3)
|
||
|
||
**Goal:** User can register, log in via email or OAuth, and create/manage sites.
|
||
|
||
#### 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, status(DRAFT/PUBLISHED), publishedAt, createdAt, updatedAt |
|
||
| `com/indie/repository/*.java` | Add | JPA repositories for User, Site |
|
||
| `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}` |
|
||
|
||
#### 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 — LLM Prompt Workspace (Target: Weeks 4-7)
|
||
|
||
**Goal:** User opens a site, types a prompt describing their desired website, optionally adds reference URLs, and the AI generates it. User can refine via follow-up prompts or edit text inline. Each generation is saved as a version in a timeline.
|
||
|
||
#### Backend
|
||
|
||
| File | Action | Details |
|
||
|------|--------|---------|
|
||
| `com/indie/service/llm/LLMProvider.java` | Add | Interface: `generate(systemPrompt, userPrompt, references, options) → LLMResult` |
|
||
| `com/indie/service/llm/GeminiProvider.java` | Add | Primary provider. Supports multimodal (text + images via URL) |
|
||
| `com/indie/service/llm/OpenAIProvider.java` | Add | Secondary/fallback |
|
||
| `com/indie/service/llm/LLMFactory.java` | Add | Provider routing + fallback chain |
|
||
| `com/indie/dto/SiteStructure.java` | Add | Top-level DTO: `List<PageStructure> pages`, `SiteTheme theme`. Each `PageStructure`: id, title, slug, `List<ComponentStructure>`. Each `ComponentStructure`: id, type, config (Map), styles (Map) |
|
||
| `com/indie/dto/Reference.java` | Add | type(URL/IMAGE), url, altText |
|
||
| `com/indie/model/GenerationVersion.java` | Add | MongoDB: sessionId, versionNumber, prompt, references(List), full SiteStructure(JSON), createdAt. Linked to a site |
|
||
| `com/indie/service/LLMService.java` | Add | Orchestrator: validate prompt → build system prompt with references → call LLM → parse → validate schema → store version → return SiteStructure |
|
||
| `com/indie/service/LLMResponseParser.java` | Add | Parse LLM JSON output, handle markdown fences, malformed fields, partial extraction, default values |
|
||
| `com/indie/controller/LLMController.java` | Add | `POST /api/llm/generate` (prompt + references → SiteStructure), `POST /api/llm/refine` (existing structure + new prompt → updated structure), `GET /api/llm/versions/{siteId}` (timeline list), `POST /api/llm/versions/{id}/restore` |
|
||
| `com/indie/service/ReferenceService.java` | Add | For URL references: fetch page content/extract info for LLM context. Upload button present for future use; for MVP, URL-only |
|
||
| `com/indie/config/RateLimitConfig.java` | Add | Rate limiting: 10 generations/hour/user |
|
||
| `src/main/resources/prompts/site-generator.txt` | Add | System prompt: schema definition + 2-3 few-shot examples (blog, portfolio, landing page) |
|
||
|
||
#### Frontend
|
||
|
||
| File | Action | Details |
|
||
|------|--------|---------|
|
||
| `src/app/workspace/workspace.component.ts` | Add | Main workspace layout: prompt area (top), preview (center-right), version timeline (right sidebar), content editor (bottom panel) |
|
||
| `src/app/workspace/prompt-input/` | Add | PromptInputComponent — expanding textarea, reference URL chip input, Upload button (file picker, MVP: URL only). Generate + Refine buttons |
|
||
| `src/app/workspace/preview/` | Add | PreviewComponent — sandboxed iframe rendering generated HTML (srcdoc). Device toggle (desktop 1200px, tablet 768px, mobile 375px). Styled to feel like a browser window |
|
||
| `src/app/workspace/version-timeline/` | Add | VersionTimelineComponent — Material vertical stepper list. Each entry: version number, short prompt summary (truncated), timestamp, thumbnail. Current version highlighted. Click to preview that version. Restore button with confirmation dialog |
|
||
| `src/app/workspace/content-editor/` | Add | ContentEditorComponent — expandable Material accordion grouped by page. Each page section contains editable fields for each text component (heading text, body text, button labels, image alt texts, etc.). Style edits (colors, fonts) go through prompts. Changes update preview in real time |
|
||
| `src/app/workspace/services/llm-session.service.ts` | Add | Manages: activeVersion, version list, current prompt, references |
|
||
| `src/app/workspace/services/workspace-state.service.ts` | Add | BehaviorSubject store: currentStructure$, versions$, selectedVersion$, draftEdits$, previewHtml$ |
|
||
| `src/app/workspace/services/preview-renderer.service.ts` | Add | Takes SiteStructure + any draft edits → generates HTML string for iframe srcdoc |
|
||
|
||
**Component types for generated sites:**
|
||
- heading — text, level (h1-h6)
|
||
- text — rich text (HTML, sanitized on output)
|
||
- image — src, alt, caption
|
||
- button — label, url, style
|
||
- divider — style, color
|
||
- gallery — image list, layout
|
||
- video — embed URL
|
||
- contact_form — fields config
|
||
- map — address, zoom
|
||
- custom_html — raw HTML
|
||
|
||
**Version storage:** Each generation attempt creates a MongoDB document (`GenerationVersion`) with the full site structure. Timeline fetched sorted by versionNumber desc. Max 100 versions per site.
|
||
|
||
---
|
||
|
||
### v0.3 — Export, Git & Hosting (Target: Weeks 8-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.
|
||
|
||
#### Backend
|
||
|
||
| File | Action | Details |
|
||
|------|--------|---------|
|
||
| `com/indie/service/RenderService.java` | Add | Core render engine: `renderSite(structure) → SiteOutput`. Takes SiteStructure → generates HTML, CSS, JS strings. Uses Thymeleaf templates per component type |
|
||
| `com/indie/engine/templates/*.html` | Add | Thymeleaf templates per component + `layout.html` (page shell) + `head.html` (SEO meta) |
|
||
| `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. Upload button present but MVP is URL-only |
|
||
| `com/indie/controller/MediaController.java` | Add | `POST /api/media/upload`, `GET /api/media/{id}` |
|
||
|
||
#### Frontend
|
||
|
||
| File | Action | Details |
|
||
|------|--------|---------|
|
||
| `src/app/workspace/git-settings/` | Add | GitSettingsComponent — connect/disconnect GitHub/GitLab OAuth, select remote repo, connection status indicator |
|
||
| `src/app/workspace/settings/` | Add | SettingsComponent — site name, export zip button, publish/unpublish buttons |
|
||
|
||
#### Infrastructure
|
||
|
||
| File | Action | Details |
|
||
|------|--------|---------|
|
||
| `infra/cloudflare-worker.js` | TBD | Routes custom domains → R2 bucket |
|
||
| `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/
|
||
```
|
||
|
||
**Output file structure (zip export):**
|
||
```
|
||
site-export/
|
||
├── index.html
|
||
├── about/index.html
|
||
├── contact/index.html
|
||
├── style.css
|
||
├── script.js
|
||
├── assets/images/
|
||
│ ├── hero.jpg
|
||
│ └── gallery-1.jpg
|
||
├── 404.html
|
||
└── .git/
|
||
```
|
||
|
||
---
|
||
|
||
### v0.4 — 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/{siteId}/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/{siteId}` → `application/rss+xml`, 1h cache |
|
||
|
||
#### Frontend
|
||
|
||
| File | Action | Details |
|
||
|------|--------|---------|
|
||
| `src/app/workspace/settings/` | Modify | Add git history panel (commit list, rollback button, download repo button) |
|
||
| `src/app/workspace/components/comments-section/` | Add | In-workspace config (enable/disable, moderate) + renderer for generated sites |
|
||
|
||
---
|
||
|
||
## Infrastructure Architecture
|
||
|
||
```
|
||
┌──────────────────────┐
|
||
│ Cloudflare CDN │
|
||
│ custom domains → R2 │
|
||
└────────┬─────────────┘
|
||
│
|
||
┌────────▼─────────────┐
|
||
│ Cloudflare R2 │
|
||
│ indie-sites bucket │
|
||
│ /{siteId}/* │
|
||
└──────────────────────┘
|
||
▲
|
||
┌────────┴─────────────┐
|
||
│ Spring Boot API │
|
||
│ (VPS / Docker) │
|
||
└────────┬─────────────┘
|
||
│
|
||
┌──────────────┼──────────────┐
|
||
│ │ │
|
||
┌────────▼───┐ ┌──────▼──────┐ ┌───▼────────┐
|
||
│ PostgreSQL │ │ MongoDB │ │ Git Repos │
|
||
│ (users, │ │ (versions, │ │ /var/git/ │
|
||
│ sites) │ │ sessions) │ └────────────┘
|
||
└─────────────┘ └─────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## LLM Provider Abstraction
|
||
|
||
```java
|
||
public interface LLMProvider {
|
||
LLMResult generate(String systemPrompt, String userPrompt,
|
||
List<Reference> references, LLMOptions options);
|
||
String getName();
|
||
}
|
||
```
|
||
|
||
```yaml
|
||
# 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
|
||
```
|
||
|
||
Adding a new provider = implement `LLMProvider` interface + add config block.
|
||
|
||
---
|
||
|
||
## 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. **Content editor** — Text edits happen in a structured form grouped by page, not inside the iframe. Style/structure changes go through prompts.
|
||
|
||
6. **No billing in MVP** — Monetization is post-MVP.
|
||
|
||
---
|
||
|
||
## Validation Strategy
|
||
|
||
| Release | What to Test |
|
||
|---------|-------------|
|
||
| v0.1 | Register, login (email + OAuth), refresh token, site CRUD, 401 on unauthenticated |
|
||
| v0.2 | Prompt → valid SiteStructure generated, preview renders correctly, version timeline shows history, restore works, content editor updates preview, refinement prompt modifies existing structure, reference URLs included in generation |
|
||
| v0.3 | Exported zip opens in browser identically to preview, git init + commit works, git push to remote works, R2 upload succeeds |
|
||
| v0.4 | Comment submit → approve → display, RSS validates, rollback restores previous version |
|
||
|
||
---
|
||
|
||
## Risk Register
|
||
|
||
| Risk | Likelihood | Mitigation |
|
||
|------|-----------|------------|
|
||
| LLM output quality inconsistent (most critical risk) | High | Invest heavily in system prompt + few-shot examples; solid response parser with fallbacks; allow manual text edits as safety net |
|
||
| Scope creep | High | Strict feature freeze per v0.x; ship as-is |
|
||
| LLM API costs | Low | Gemini-2.0-flash is free-tier friendly; rate limit 10/hour/user |
|
||
| Solo developer burnout | Medium | Realistic timeline; 4 releases instead of 5 saves ~2 weeks |
|