remove subdomain feature
This commit is contained in:
22
PLAN.md
22
PLAN.md
@@ -1,6 +1,6 @@
|
||||
# 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. Users self-host or use free subdomain hosting.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -75,7 +75,7 @@ Prompt + Reference URLs
|
||||
| `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), status(DRAFT/PUBLISHED), publishedAt, 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 |
|
||||
@@ -151,7 +151,7 @@ Prompt + Reference URLs
|
||||
|
||||
### 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. Sites served at `*.indie.com` for free.
|
||||
**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
|
||||
|
||||
@@ -178,13 +178,13 @@ Prompt + Reference URLs
|
||||
| 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, subdomain, export zip button, publish/unpublish buttons |
|
||||
| `src/app/workspace/settings/` | Add | SettingsComponent — site name, export zip button, publish/unpublish buttons |
|
||||
|
||||
#### Infrastructure
|
||||
|
||||
| File | Action | Details |
|
||||
|------|--------|---------|
|
||||
| `infra/cloudflare-worker.js` | Add | Routes `*.indie.com` → R2 bucket `indie-sites/{subdomain}/` |
|
||||
| `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:**
|
||||
@@ -225,9 +225,9 @@ site-export/
|
||||
| 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/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/{subdomain}` → `application/rss+xml`, 1h cache |
|
||||
| `com/indie/controller/RssController.java` | Add | `GET /api/feed/{siteId}` → `application/rss+xml`, 1h cache |
|
||||
|
||||
#### Frontend
|
||||
|
||||
@@ -243,13 +243,13 @@ site-export/
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ Cloudflare CDN │
|
||||
│ *.indie.com → R2 │
|
||||
│ custom domains → R2 │
|
||||
└────────┬─────────────┘
|
||||
│
|
||||
┌────────▼─────────────┐
|
||||
│ Cloudflare R2 │
|
||||
│ indie-sites bucket │
|
||||
│ /{subdomain}/* │
|
||||
│ /{siteId}/* │
|
||||
└──────────────────────┘
|
||||
▲
|
||||
┌────────┴─────────────┐
|
||||
@@ -309,7 +309,7 @@ Adding a new provider = implement `LLMProvider` interface + add config block.
|
||||
|
||||
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** — Subdomain hosting is free. Monetization is post-MVP.
|
||||
6. **No billing in MVP** — Monetization is post-MVP.
|
||||
|
||||
---
|
||||
|
||||
@@ -319,7 +319,7 @@ Adding a new provider = implement `LLMProvider` interface + add config block.
|
||||
|---------|-------------|
|
||||
| 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, subdomain serves content |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ export interface SiteResponse {
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
subdomain: string;
|
||||
publishedUrl: string | null;
|
||||
themeConfig: Record<string, unknown>;
|
||||
status: 'DRAFT' | 'PUBLISHED';
|
||||
@@ -16,7 +15,6 @@ export interface SiteResponse {
|
||||
export interface SiteRequest {
|
||||
name: string;
|
||||
description: string;
|
||||
subdomain: string;
|
||||
deploymentTarget?: string;
|
||||
themeConfig?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -21,15 +21,6 @@ import { SiteService } from '../../core/services/site.service';
|
||||
<mat-error>Name is required</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="w-full">
|
||||
<mat-label>Subdomain</mat-label>
|
||||
<input matInput formControlName="subdomain" placeholder="my-site" />
|
||||
<mat-error *ngIf="form.get('subdomain')?.hasError('required')">Subdomain is required</mat-error>
|
||||
<mat-error *ngIf="form.get('subdomain')?.hasError('pattern')">
|
||||
Lowercase letters, numbers, and hyphens only
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="w-full">
|
||||
<mat-label>Description (optional)</mat-label>
|
||||
<textarea matInput formControlName="description" rows="3" placeholder="What's your site about?"></textarea>
|
||||
@@ -51,7 +42,6 @@ export class CreateSiteDialog {
|
||||
|
||||
form = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
subdomain: ['', [Validators.pattern(/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/)]],
|
||||
description: [''],
|
||||
});
|
||||
loading = false;
|
||||
|
||||
@@ -61,7 +61,6 @@ import { DeleteSiteDialog } from './delete-site-dialog/delete-site-dialog';
|
||||
<mat-card *ngFor="let site of sites" class="cursor-pointer hover:shadow-lg transition-shadow" (click)="openBuilder(site)">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ site.name }}</mat-card-title>
|
||||
<mat-card-subtitle *ngIf="site.subdomain">{{ site.subdomain }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p class="text-gray-600 text-sm mt-2">{{ site.description || 'No description' }}</p>
|
||||
|
||||
@@ -56,10 +56,6 @@ import { SelfHostedConfigResponse } from '../../core/models/deployment';
|
||||
<mat-label>Description</mat-label>
|
||||
<textarea matInput rows="2" [(ngModel)]="siteDescription" [disabled]="saving"></textarea>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="w-full">
|
||||
<mat-label>Subdomain</mat-label>
|
||||
<input matInput [(ngModel)]="siteSubdomain" [disabled]="saving" />
|
||||
</mat-form-field>
|
||||
<div class="flex justify-end">
|
||||
<button mat-flat-button color="primary" [disabled]="saving || !siteName.trim()" (click)="saveSettings()">
|
||||
<mat-icon>save</mat-icon>
|
||||
@@ -316,7 +312,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
siteId: string | null = null;
|
||||
siteName = '';
|
||||
siteDescription = '';
|
||||
siteSubdomain = '';
|
||||
siteStatus: 'DRAFT' | 'PUBLISHED' = 'DRAFT';
|
||||
liveUrl = '';
|
||||
deploymentTarget: 'GIT_PAGES' | 'SELF_HOSTED' | 'NONE' = 'NONE';
|
||||
@@ -370,7 +365,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
next: (site) => {
|
||||
this.siteName = site.name;
|
||||
this.siteDescription = site.description || '';
|
||||
this.siteSubdomain = site.subdomain || '';
|
||||
this.siteStatus = site.status;
|
||||
this.liveUrl = site.publishedUrl || '';
|
||||
this.deploymentTarget = site.deploymentTarget || 'NONE';
|
||||
@@ -491,7 +485,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.siteService.update(this.siteId, {
|
||||
name: this.siteName,
|
||||
description: this.siteDescription,
|
||||
subdomain: this.siteSubdomain,
|
||||
deploymentTarget: this.deploymentTarget,
|
||||
}).pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
@@ -506,7 +499,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.siteService.update(this.siteId, {
|
||||
name: this.siteName,
|
||||
description: this.siteDescription,
|
||||
subdomain: this.siteSubdomain,
|
||||
deploymentTarget: this.deploymentTarget,
|
||||
}).pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
@@ -593,7 +585,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const name = this.siteSubdomain || this.siteId || 'site';
|
||||
const name = this.siteId || 'site';
|
||||
a.download = `site-export-${name}.zip`;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
Reference in New Issue
Block a user