A FastAPI + React + Vite blog and admin console. It supports public posts, user accounts, author pages, review workflow, admin statistics, comments, operation logs, tags, site settings, and local development seed data.
- Backend: FastAPI, SQLAlchemy, SQLite
- Frontend: React, TypeScript, Vite
- Tests: pytest
app/
core/ # Configuration
db/ # SQLAlchemy session, models, bootstrap/migrations
routes/ # FastAPI route modules
services/ # Business services
static/ # Legacy static assets
templates/ # Legacy Jinja templates
frontend/
src/ # React application
admin.html # Admin console entry
index.html # Public site entry
scripts/
seed_fake_data.py # Local development data generator
tests/
test_health.py # API regression tests
Copy .env.example to .env if you want to override defaults:
APP_NAME=SkillTree Blog
APP_ENV=development
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
ADMIN_USERNAME=admin
ADMIN_PASSWORD=skilltree123
SECRET_KEY=please-change-this-secret
DATABASE_URL=sqlite:///data/skilltree.sqlite3
AVATAR_UPLOAD_DIR=app/static/uploads/avatars
CARD_BACKGROUND_UPLOAD_DIR=app/static/uploads/card-backgrounds
POST_ASSET_UPLOAD_DIR=app/static/uploads/post-assetsDefault admin account:
- Username:
admin - Password:
skilltree123
Change ADMIN_PASSWORD and SECRET_KEY before using this outside local development.
Production startup validates the critical settings when APP_ENV=production:
SECRET_KEYmust be changed and contain at least 32 characters.ADMIN_PASSWORDmust be changed and contain at least 12 characters.CORS_ORIGINSmust be explicit and cannot contain*.DATABASE_URLmust point to a persistent database.AVATAR_UPLOAD_DIR,CARD_BACKGROUND_UPLOAD_DIR, andPOST_ASSET_UPLOAD_DIRcontrol where uploaded avatars, background materials, and post body images are stored.
Example production-style values:
APP_ENV=production
CORS_ORIGINS=https://your-domain.example
ADMIN_PASSWORD=replace-with-a-long-random-password
SECRET_KEY=replace-with-at-least-32-random-characters
DATABASE_URL=sqlite:////opt/skilltree/data/skilltree.sqlite3
AVATAR_UPLOAD_DIR=/opt/skilltree/app/static/uploads/avatars
CARD_BACKGROUND_UPLOAD_DIR=/opt/skilltree/app/static/uploads/card-backgrounds
POST_ASSET_UPLOAD_DIR=/opt/skilltree/app/static/uploads/post-assetsBackend:
E:\Anaconda3\envs\SkillTree\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000Frontend:
cd frontend
npm install
npm run dev -- --host 127.0.0.1Open:
- Public site:
http://127.0.0.1:5173/ - Admin console:
http://127.0.0.1:5173/admin.html - Health check:
http://127.0.0.1:8000/health
Generate repeatable local development data:
E:\Anaconda3\envs\SkillTree\python.exe scripts\seed_fake_data.pyThe script resets and recreates:
- Development users:
dev_author_alice,dev_author_bob,dev_author_disabled - Development content with
dev-sample-*slugs - Comments for moderation
- Tags for the tag manager
Backend tests:
E:\Anaconda3\envs\SkillTree\python.exe -m pytestFrontend build:
cd frontend
npm run buildTests use an isolated temporary SQLite database through tests/conftest.py, so they do not mutate data/skilltree.sqlite3.
Create or upgrade the database tables and seed the default admin account/categories:
E:\Anaconda3\envs\SkillTree\python.exe scripts\manage_db.py upgrade --seedCheck which schema migrations have already been applied:
E:\Anaconda3\envs\SkillTree\python.exe scripts\manage_db.py statusThe deploy scripts run upgrade --seed automatically before starting services. The migration command is safe to run repeatedly; applied versions are tracked in the schema_migrations table.
Create a backup zip containing the SQLite database, uploaded avatars, background materials, post body assets, and a manifest:
E:\Anaconda3\envs\SkillTree\python.exe scripts\backup_data.py backupRestore from a backup:
E:\Anaconda3\envs\SkillTree\python.exe scripts\backup_data.py restore backups\skilltree-backup-YYYYMMDD-HHMMSS.zip --yesRestore replaces the current database and upload directories. By default it first creates a safety backup in backups/; add --no-safety-backup only if you already made a separate copy. Stop the running backend before restoring production data.
After starting the backend, run a quick API smoke check:
E:\Anaconda3\envs\SkillTree\python.exe scripts\smoke_check.pyThe script verifies health check, admin login, public posts, admin stats, tags, comments, logs, and settings.
For a single Windows machine or small server, use the deploy script to test, build, restart services, and run smoke checks:
powershell.exe -ExecutionPolicy Bypass -File scripts\deploy.ps1 -Python "E:\Anaconda3\envs\SkillTree\python.exe"Default ports:
- Backend:
http://127.0.0.1:8000 - Frontend production preview:
http://127.0.0.1:4173
Useful options:
# Build and test only, do not start services
powershell.exe -ExecutionPolicy Bypass -File scripts\deploy.ps1 -NoStart
# Skip tests when doing a quick restart
powershell.exe -ExecutionPolicy Bypass -File scripts\deploy.ps1 -SkipTests
# Seed local fake data before starting
powershell.exe -ExecutionPolicy Bypass -File scripts\deploy.ps1 -SeedFakeData
# Expose services on all interfaces for LAN/server use
powershell.exe -ExecutionPolicy Bypass -File scripts\deploy.ps1 -BackendHost 0.0.0.0 -FrontendHost 0.0.0.0Logs are written to logs/backend.*.log and logs/frontend.*.log.
On Linux, the Bash deploy script can create/update a virtualenv, install Python and Node dependencies, run database migrations, verify writable upload/data directories, run tests, build the frontend, restart services, and run smoke checks:
chmod +x scripts/deploy.sh
BACKEND_HOST=0.0.0.0 FRONTEND_HOST=0.0.0.0 scripts/deploy.shPrerequisites:
- Python 3.11+
- Node.js and npm
curllsoforssfor stopping existing processes on the target ports
Common options:
# Install dependencies, test, and build only
scripts/deploy.sh --no-start
# Quick restart without tests
scripts/deploy.sh --skip-tests
# Seed fake data before starting
scripts/deploy.sh --seed-fake-data
# Use a specific Python executable
PYTHON_BIN=python3.11 scripts/deploy.shThe Linux script uses .venv by default. Override with SKILLTREE_VENV_DIR=/path/to/venv.
Public:
GET /healthGET /api/postsGET /api/posts?type=article&q=keyword&date=2026-04-24GET /api/posts/{slug}GET /api/authors/{username}
Auth:
POST /api/auth/loginPOST /api/auth/registerGET /api/auth/mePATCH /api/auth/me
Content:
GET /api/me/postsGET /api/me/posts/{slug}POST /api/me/posts/{slug}/withdrawGET /api/admin/postsPOST /api/admin/postsPATCH /api/admin/posts/{slug}DELETE /api/admin/posts/{slug}POST /api/admin/posts/{slug}/review
Admin:
GET /api/admin/statsGET /api/admin/usersPATCH /api/admin/users/{user_id}DELETE /api/admin/users/{user_id}GET /api/admin/commentsPATCH /api/admin/comments/{comment_id}DELETE /api/admin/comments/{comment_id}GET /api/admin/logsGET /api/admin/tagsPOST /api/admin/tagsPATCH /api/admin/tags/{tag_id}DELETE /api/admin/tags/{tag_id}GET /api/admin/settingsPUT /api/admin/settings
- Deleting users performs a soft delete and keeps content author references intact.
- Public posts only include
status=publishedandvisibility=public. - Author-created content goes through
draft -> pending -> published/rejected/archived. - Admin stats and dashboard charts are backed by real API data.