chore: cleanup unused files and update gitignore
- Remove MONETIZATION.md and README.md - Add dist/ to gitignore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ac04d7f346
commit
58fd9c0a55
9 changed files with 35 additions and 194 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -10,4 +10,7 @@ node_modules/
|
|||
.vscode/
|
||||
*.swp
|
||||
|
||||
dist/
|
||||
|
||||
tmp
|
||||
.vite/
|
||||
|
|
|
|||
16
Dockerfile
16
Dockerfile
|
|
@ -1,16 +1,20 @@
|
|||
FROM node:22-alpine AS studio
|
||||
WORKDIR /app/studio
|
||||
COPY studio/package*.json ./
|
||||
FROM node:22-alpine AS frontends
|
||||
WORKDIR /app
|
||||
COPY frontends/package*.json ./
|
||||
COPY frontends/studio/package*.json ./studio/
|
||||
COPY frontends/owner-tools/package*.json ./owner-tools/
|
||||
COPY frontends/ui/package.json ./ui/
|
||||
RUN npm ci
|
||||
COPY studio/ ./
|
||||
RUN npm run build
|
||||
COPY frontends/ ./
|
||||
RUN npm run build --workspace=studio --workspace=owner-tools
|
||||
|
||||
FROM golang:1.24-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
COPY --from=studio /app/studio/dist ./studio/dist
|
||||
COPY --from=frontends /app/studio/dist ./frontends/studio/dist
|
||||
COPY --from=frontends /internal/tenant/assets/js/owner-tools.js ./internal/tenant/assets/js/owner-tools.js
|
||||
RUN CGO_ENABLED=0 go build -o writekit .
|
||||
|
||||
FROM alpine:3.21
|
||||
|
|
|
|||
121
MONETIZATION.md
121
MONETIZATION.md
|
|
@ -1,121 +0,0 @@
|
|||
# WriteKit Monetization
|
||||
|
||||
## Tiers
|
||||
|
||||
| Tier | Price | Billing |
|
||||
|------|-------|---------|
|
||||
| Free | $0 | - |
|
||||
| Pro | $5/mo or $49/year | Lemon Squeezy |
|
||||
|
||||
## Feature Matrix
|
||||
|
||||
| Feature | Free | Pro |
|
||||
|---------|------|-----|
|
||||
| Subdomain (you.writekit.dev) | Yes | Yes |
|
||||
| Custom domain | No | Yes |
|
||||
| "Powered by WriteKit" badge | Required | Hidden |
|
||||
| Analytics retention | 7 days | 90 days |
|
||||
| API requests | 100/hour | 1000/hour |
|
||||
| Webhooks | 3 endpoints | 10 endpoints |
|
||||
| Webhook deliveries | 100/day | 1000/day |
|
||||
| Plugins | 3 max | 10 max |
|
||||
| Plugin executions | 1000/day | 10000/day |
|
||||
| Posts | Unlimited | Unlimited |
|
||||
| Assets | Unlimited | Unlimited |
|
||||
| Comments/Reactions | Unlimited | Unlimited |
|
||||
|
||||
## Upgrade Triggers
|
||||
|
||||
1. **Custom domain** - Primary trigger, most valuable to users
|
||||
2. **Remove badge** - Secondary, vanity upgrade
|
||||
3. **Analytics depth** - 7 days feels limiting for serious bloggers
|
||||
4. **Rate limits** - For power users/headless CMS use case
|
||||
|
||||
## Positioning
|
||||
|
||||
**Tagline:** "Your blog, your domain, your data."
|
||||
|
||||
**Key messages:**
|
||||
- No paywalls, no algorithms, no surprises
|
||||
- 70% cheaper than Ghost ($5 vs $18/mo)
|
||||
- Own your content, export anytime
|
||||
- API-first, developer-friendly
|
||||
|
||||
## Implementation
|
||||
|
||||
### Backend Config
|
||||
|
||||
```go
|
||||
// internal/config/tiers.go
|
||||
type Tier string
|
||||
|
||||
const (
|
||||
TierFree Tier = "free"
|
||||
TierPro Tier = "pro"
|
||||
)
|
||||
|
||||
type TierConfig struct {
|
||||
Price int // cents/month
|
||||
AnnualPrice int // cents/year
|
||||
CustomDomain bool
|
||||
BadgeRequired bool
|
||||
AnalyticsRetention int // days
|
||||
APIRateLimit int // requests/hour
|
||||
MaxWebhooks int
|
||||
WebhookDeliveries int // per day
|
||||
MaxPlugins int
|
||||
PluginExecutions int // per day
|
||||
}
|
||||
|
||||
var Tiers = map[Tier]TierConfig{
|
||||
TierFree: {
|
||||
Price: 0,
|
||||
AnnualPrice: 0,
|
||||
CustomDomain: false,
|
||||
BadgeRequired: true,
|
||||
AnalyticsRetention: 7,
|
||||
APIRateLimit: 100,
|
||||
MaxWebhooks: 3,
|
||||
WebhookDeliveries: 100,
|
||||
MaxPlugins: 3,
|
||||
PluginExecutions: 1000,
|
||||
},
|
||||
TierPro: {
|
||||
Price: 500, // $5
|
||||
AnnualPrice: 4900, // $49
|
||||
CustomDomain: true,
|
||||
BadgeRequired: false,
|
||||
AnalyticsRetention: 90,
|
||||
APIRateLimit: 1000,
|
||||
MaxWebhooks: 10,
|
||||
WebhookDeliveries: 1000,
|
||||
MaxPlugins: 10,
|
||||
PluginExecutions: 10000,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Database
|
||||
|
||||
Add to `tenants` table:
|
||||
```sql
|
||||
tier TEXT NOT NULL DEFAULT 'free'
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
Update `BillingPage.tsx` with:
|
||||
- Current plan display
|
||||
- Feature comparison
|
||||
- Upgrade button → Lemon Squeezy checkout
|
||||
|
||||
### Enforcement Points
|
||||
|
||||
| Feature | File | Check |
|
||||
|---------|------|-------|
|
||||
| Custom domain | `server/domain.go` | Block if tier != pro |
|
||||
| Badge | Blog templates | Show if tier == free |
|
||||
| Analytics | `tenant/analytics.go` | Filter by retention days |
|
||||
| API rate limit | `server/middleware.go` | Rate limiter per tier |
|
||||
| Webhooks count | `tenant/webhooks.go` | Check on create |
|
||||
| Plugins count | `tenant/plugins.go` | Check on create |
|
||||
61
README.md
61
README.md
|
|
@ -1,61 +0,0 @@
|
|||
# WriteKit - Local Development
|
||||
|
||||
## Prerequisites
|
||||
- Docker & Docker Compose
|
||||
- GitHub OAuth App (for login)
|
||||
|
||||
## Setup GitHub OAuth
|
||||
1. Go to https://github.com/settings/developers
|
||||
2. New OAuth App:
|
||||
- Name: `WriteKit Local`
|
||||
- Homepage: `http://writekit.lvh.me`
|
||||
- Callback: `http://writekit.lvh.me/auth/github/callback`
|
||||
3. Copy Client ID and Secret
|
||||
|
||||
## Run
|
||||
```bash
|
||||
# Set OAuth credentials
|
||||
export GITHUB_CLIENT_ID=your_client_id
|
||||
export GITHUB_CLIENT_SECRET=your_client_secret
|
||||
|
||||
# Start
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
Or create `.env` file:
|
||||
```
|
||||
GITHUB_CLIENT_ID=your_client_id
|
||||
GITHUB_CLIENT_SECRET=your_client_secret
|
||||
```
|
||||
|
||||
## Access
|
||||
- **Platform**: http://writekit.lvh.me
|
||||
- **Traefik dashboard**: http://localhost:8080
|
||||
- **MinIO console**: http://localhost:9001 (minioadmin/minioadmin)
|
||||
|
||||
## Create a demo
|
||||
```bash
|
||||
curl -X POST http://writekit.lvh.me/api/demo
|
||||
```
|
||||
Returns subdomain like `demo-abc123.writekit.lvh.me` - works automatically, no hosts file needed.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Required
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `DATABASE_URL` | PostgreSQL connection string |
|
||||
| `DOMAIN` | Base domain |
|
||||
| `BASE_URL` | Full URL for OAuth callbacks |
|
||||
| `SESSION_SECRET` | Cookie encryption (32+ chars) |
|
||||
| `GITHUB_CLIENT_ID` | GitHub OAuth client ID |
|
||||
| `GITHUB_CLIENT_SECRET` | GitHub OAuth secret |
|
||||
|
||||
### Optional
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `GOOGLE_CLIENT_ID/SECRET` | Google OAuth |
|
||||
| `DISCORD_CLIENT_ID/SECRET` | Discord OAuth |
|
||||
| `R2_*` | Cloudflare R2 storage |
|
||||
| `IMAGINARY_URL` | Image processing service |
|
||||
| `CLOUDFLARE_API_TOKEN/ZONE_ID` | Analytics |
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"prebuild": "unocss \"src/**/*.tsx\" -o src/uno-generated.css",
|
||||
"build": "vite build",
|
||||
"watch": "vite build --watch"
|
||||
},
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
"@iconify-json/lucide": "^1.2.82",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@unocss/cli": "^66.5.12",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vite": "^6.0.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import { App } from './App'
|
||||
import reset from '@unocss/reset/tailwind.css?inline'
|
||||
import css from 'virtual:uno.css?inline'
|
||||
import css from './uno-generated.css?inline'
|
||||
|
||||
function mount() {
|
||||
const host = document.createElement('div')
|
||||
|
|
|
|||
2
frontends/owner-tools/src/uno-generated.css
Normal file
2
frontends/owner-tools/src/uno-generated.css
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/* Auto-generated by UnoCSS CLI - do not edit manually */
|
||||
/* Run: npm run prebuild to regenerate */
|
||||
|
|
@ -4,11 +4,21 @@ import UnoCSS from 'unocss/vite'
|
|||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig(({ command }) => ({
|
||||
plugins: [UnoCSS(), ...(command === 'build' ? [react()] : [])],
|
||||
plugins: [
|
||||
// Only use UnoCSS plugin in dev mode for HMR
|
||||
...(command === 'serve' ? [UnoCSS()] : []),
|
||||
...(command === 'build' ? [react()] : []),
|
||||
],
|
||||
base: command === 'serve' ? '/@owner-tools' : '/',
|
||||
esbuild: {
|
||||
jsxInject: `import React from 'react'`
|
||||
},
|
||||
resolve: {
|
||||
alias: command === 'serve' ? {
|
||||
// In dev mode, alias the generated file to virtual:uno.css
|
||||
'./uno-generated.css': 'virtual:uno.css',
|
||||
} : {},
|
||||
},
|
||||
|
||||
...(command === 'serve' && {
|
||||
server: {
|
||||
|
|
@ -36,7 +46,7 @@ export default defineConfig(({ command }) => ({
|
|||
formats: ['iife'],
|
||||
fileName: () => 'owner-tools.js'
|
||||
},
|
||||
outDir: '../../internal/build/assets/js',
|
||||
emptyOutDir: false
|
||||
outDir: '../../internal/tenant/assets/js',
|
||||
emptyOutDir: false,
|
||||
}
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'
|
|||
import { getLanguageIconUrl, getLanguageDisplayName, SUPPORTED_LANGUAGES } from './icons'
|
||||
import { Icons } from '../../../shared/Icons'
|
||||
|
||||
export function CodeBlockView({ node, updateAttributes, extension }: NodeViewProps) {
|
||||
export function CodeBlockView({ node, updateAttributes }: NodeViewProps) {
|
||||
const { language, title } = node.attrs
|
||||
const [showLanguageMenu, setShowLanguageMenu] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
|
@ -161,7 +161,9 @@ export function CodeBlockView({ node, updateAttributes, extension }: NodeViewPro
|
|||
|
||||
{/* Code content */}
|
||||
<pre className="!mt-0 !rounded-t-none">
|
||||
<NodeViewContent as="code" className={language ? `language-${language}` : ''} />
|
||||
<code className={language ? `language-${language}` : ''}>
|
||||
<NodeViewContent />
|
||||
</code>
|
||||
</pre>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue