| task | test_command | completion_criteria | max_iterations | |||||
|---|---|---|---|---|---|---|---|---|
Production-Grade Self-Hosted Tina CMS |
cd apps/media && pnpm dev |
|
50 |
Implement a secure, self-hosted Tina CMS backend for apps/media, replacing
TinaCloud and incorporating all security, operational, and UX considerations.
This will allow Solana Foundation content editors to manage content securely via
GitHub and Upstash Redis, moving away from third-party hosting for the CMS data
layer.
-
Secure Authentication
- Implement Google OAuth using Auth.js v5 (NextAuth).
- Restrict access to
@solana.orgemails and specified whitelisted users. - Protect
/admin/*and/api/tina/*routes using JWT-based sessions.
-
Content Persistence
- Use the GitHub API to commit content changes directly to the
stagingbranch. - Support standard CRUD operations for posts, podcasts, and other CMS collections.
- Handle media uploads by saving to
public/uploads/and committing them to the repository.
- Use the GitHub API to commit content changes directly to the
-
Data Indexing
- Implement a local data layer using Upstash Redis (via
upstash-redis-level). - Support full content indexing from the filesystem for local development and build steps.
- Support incremental re-indexing via GitHub webhooks for production environments.
- Implement a local data layer using Upstash Redis (via
-
Draft & Preview Mode
- Implement Next.js Draft Mode for real-time content previews.
- Use secure, signed tokens (PREVIEW_SECRET) to protect draft access.
- Provide a "Preview" button in the Tina Admin interface.
- Compatibility: Must work with Next.js 15 App Router.
- Security: Mandatory email verification and domain restriction for all editor access.
- Performance: High-speed GraphQL queries served from the Redis index.
- Resilience: Graceful fallbacks to static content if the CMS backend is unreachable.
- Framework: Next.js 15
- Auth: Auth.js v5 with npm package
tinacms/authjsbridge - Storage: GitHub API (@octokit/rest)
- Indexing: Upstash Redis (REST API)
- Branching: Target staging branch for all CMS-driven commits
- Install required packages:
next-auth,tinacms/authjs,@upstash/redis,upstash-redis-level,@octokit/rest - Update
.env.examplewith all self-hosted environment variables - Update
turbo.jsonglobalEnv (Remove TinaCloud vars, add Auth/Redis/GitHub vars) - Update
package.jsonscripts fordev,build, anddb:init
- Create
apps/media/auth.tswith Google OAuth and @solana.org restriction - Implement
api/auth/[...nextauth]routes - Protect
/adminand/api/tinainmiddleware.ts - Create login UI at
app/admin/login/page.tsx
- Implement
tina/database.ts(TinaLevelClient + Redis/Filesystem) - Implement
tina/github-provider.tsfor GitHub API persistence - Update
tina/config.tsxto use self-hosted GQL and Auth provider - Create
db:initscript for content indexing
- Create
/api/tina/gqlroute for handling CMS queries/mutations - Implement
/api/tina/[...paths]for media and schema handling - Implement mutation-level authentication
- Create
/api/tina/webhookfor GitHub re-indexing - Implement custom media handler for Git-backed uploads
- Implement
/api/draftand/api/draft/disableroutes - Update page components to support
draftMode().isEnabled - Add "Preview" button to Tina Admin edit interface
- Update
scripts/build.shto include database warm-up - Update
README.md,DEPLOYMENT.md, andMIGRATION.md - Complete end-to-end verification of the editor workflow
┌─────────────────────────────────────────────────────────────────────────┐
│ apps/media │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ /admin │────▶│ Auth.js v5 │────▶│ Google OAuth │ │
│ │ (TinaCMS) │ │ (auth.ts) │ │ (@solana.org)│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ /api/tina/gql│────▶│ Tina Database│────▶│ Upstash Redis│ │
│ │ (GraphQL) │ │ (database.ts)│ │ (indexing) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ GitHubProvider────▶│ GitHub API │──▶ staging branch │
│ │ (mutations) │ │ (@octokit) │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
| Variable | Description |
|---|---|
AUTH_SECRET |
Auth.js v5 secret (openssl rand -base64 32) |
AUTH_GOOGLE_ID |
Google Cloud Console Client ID |
AUTH_GOOGLE_SECRET |
Google Cloud Console Client Secret |
AUTH_WHITELIST |
Comma-separated list of extra allowed emails |
KV_REST_API_URL |
Upstash Redis REST URL |
KV_REST_API_TOKEN |
Upstash Redis REST Token |
GITHUB_PERSONAL_ACCESS_TOKEN |
GitHub PAT with repo scope |
GITHUB_REPO_OWNER |
solana-foundation |
GITHUB_REPO_NAME |
solana-com |
GITHUB_BRANCH |
staging |
PREVIEW_SECRET |
Secret for Next.js Draft Mode tokens |
# Initialize local database and index all markdown files
pnpm db:init
# Run the media app with self-hosted Tina
cd apps/media && pnpm dev
# Build the app (includes indexing step)
cd apps/media && pnpm buildWhen working on this task:
- Read
.ralph/progress.mdto see what's been done - Check
.ralph/guardrails.mdfor signs to follow - Work on the next incomplete criterion from the checklist above
- Update
.ralph/progress.mdwith your progress - Commit your changes with descriptive messages
- Run tests frequently to verify progress
- When ALL criteria are met (all
[ ]→[x]), output:<ralph>COMPLETE</ralph> - If stuck on the same issue 3+ times, output:
<ralph>GUTTER</ralph>