A full-stack application to create, edit, and display interactive markmaps.
- Backend: NestJS REST API with PostgreSQL database
- Frontend: Nuxt.js 3 web application with optional HTTPS support
- Cloudflare Turnstile: Anti-bot protection for signup and login
- Email Verification: Required email verification for new accounts
- Password Security: Bcrypt password hashing with salt
- JWT Authentication: Secure token-based authentication
- HTTPS Support: Optional SSL/TLS encryption for production
- HTTP/HTTPS Support: Runs on HTTP by default, optional HTTPS with self-signed certificates
- Interactive UI: Modern, responsive web interface
- Markmap Viewer: Interactive visualization of markmaps
- Live Editor: Create and edit markmaps with real-time preview
- User Authentication: Login, signup, and profile management
- Search Interface: Find markmaps by title, content, language, or tags
- Database: Prisma with PostgreSQL
- Authentication: JWT-based authentication system
- REST API: Full CRUD operations for markmaps
- Email Service: Nodemailer integration for verification emails
- View markmaps without login required
- Markmap autoloader for easy visualization
- Public sharing of markmaps
- Human-friendly URLs: Access markmaps via
/{username}/{slug}(e.g.,/markmaps/johndoe/my-learning-path) - Fullscreen view: Display markmaps in fullscreen mode with
/fullscreensuffix - Copy direct link: Easy sharing with a button to copy fullscreen link
- Search markmaps by:
- Title or text content
- Language tags
- Custom tags (e.g., #javascript, #tutorial)
- Sign up with email verification
- Login with Cloudflare Turnstile bot protection
- JWT token-based authentication
- Secure password hashing with bcrypt
- Email verification required before full account access
- Viewing History: Track all viewed markmaps
- Interaction History: Record user interactions (expand, collapse, search, etc.)
- User profile with statistics
- Markmap Editor: Create and edit markmaps with:
- Text: Markdown content for markmap
- Tags:
- Language tags
- Custom Tags: Add custom tags starting with # (e.g., #javascript, #tutorial)
- Autocomplete suggestions with usage counts
- Create new tags on-the-fly
- Parameters:
maxWidth: Maximum width for nodescolorFreezeLevel: Level at which colors freezeinitialExpandLevel: Initial expansion level (-1 for fully expanded)
- Full CRUD operations (Create, Read, Update, Delete)
- Tag Management:
- All tags must start with # (automatically normalized)
- Autocomplete suggestions while typing
- Shows existing tag usage counts
- Option to create new tags
- Tag Analytics Page (
/tags):- Trending Tags: Bar chart showing top 10 most popular tags
- Filter by time period: All Time, Last 24 Hours, Last Hour
- Automatically refreshes
- Historical Trends: Line chart showing tag usage over last 30 days
- Search for specific tags
- Visual representation of tag growth/decline
- Trending Tags: Bar chart showing top 10 most popular tags
npm installCreate a .env file in the root directory based on .env.example:
DATABASE_URL="postgresql://user:password@localhost:5432/midiverse?schema=public"
JWT_SECRET="your-secret-key-change-this-in-production"
JWT_EXPIRATION="7d"
PORT=3000
# Cloudflare Turnstile (get keys from https://dash.cloudflare.com/)
TURNSTILE_SECRET_KEY="your-turnstile-secret-key"
# Email Configuration (for email verification)
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT=587
EMAIL_SECURE=false
EMAIL_USER="your-email@gmail.com"
EMAIL_PASSWORD="your-app-password"
EMAIL_FROM="noreply@midiverse.com"
# Application URL (used in email verification links)
EMAIL_LINK_BASEURL="http://localhost:3001"Note:
- Get Cloudflare Turnstile keys from Cloudflare Dashboard
- For Gmail, create an App Password
- In development, Turnstile verification is optional (skipped if not configured)
- Email verification is required for all new accounts
Create a .env file in the frontend directory:
NUXT_PUBLIC_API_BASE=http://localhost:3000/api
NUXT_PUBLIC_TURNSTILE_SITE_KEY=your-turnstile-site-keyNote:
- Get the Turnstile Site Key (not Secret Key) from Cloudflare Dashboard
- For local testing without Turnstile: Leave
NUXT_PUBLIC_TURNSTILE_SITE_KEYempty or set to empty string. The app will show a development warning and allow signup/login without bot protection. - For production: Always configure Turnstile keys for bot protection
If you want to run the frontend with HTTPS, generate self-signed SSL certificates:
cd frontend
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes -subj "/CN=localhost"For detailed database setup instructions, especially for fresh/empty databases, see PRISMA_SETUP.md.
# Generate Prisma Client (automatically runs after npm install)
npx prisma generate
# Apply migrations to a fresh database
npx prisma migrate deploy
# Or for development (creates database if needed)
npx prisma migrate devNote: If you encounter migration errors with an existing database, see the troubleshooting section in PRISMA_SETUP.md.
# Development
npm run start:dev
# Production
npm run build
npm run start:prodThe backend will be available at http://localhost:3000/api
cd frontend
npm install
npm run devThe frontend will be available at http://localhost:3001 (or https://localhost:3001 if you generated SSL certificates)
Note: If using HTTPS with self-signed certificates, your browser will show a security warning - this is normal for development. Click "Advanced" and proceed to continue.
-
Start the backend in one terminal:
npm run start:dev
-
Start the frontend in another terminal:
cd frontend npm run dev -
Access the application at
https://localhost:3001
POST /auth/signup
Content-Type: application/json
{
"email": "user@example.com",
"username": "username",
"password": "password123"
}POST /auth/login
Content-Type: application/json
{
"username": "username",
"password": "password123"
}Returns:
{
"access_token": "jwt-token",
"user": {
"id": "uuid",
"email": "user@example.com",
"username": "username"
}
}POST /markmaps
Authorization: Bearer {token}
Content-Type: application/json
{
"title": "My Markmap",
"text": "# Root\n## Branch 1\n## Branch 2",
"language": "en",
"tags": ["#tutorial", "#javascript"],
"maxWidth": 300,
"colorFreezeLevel": 2,
"initialExpandLevel": -1,
"isPublic": true
}GET /markmapsGET /markmaps/search?query=tutorial&language=enGET /markmaps/{id}PATCH /markmaps/{id}
Authorization: Bearer {token}
Content-Type: application/json
{
"title": "Updated Title",
"text": "# Updated content"
}DELETE /markmaps/{id}
Authorization: Bearer {token}POST /markmaps/{id}/interactions
Content-Type: application/json
{
"type": "expand",
"metadata": {
"nodeId": "node-1"
}
}GET /users/profile
Authorization: Bearer {token}GET /users/markmaps
Authorization: Bearer {token}GET /users/history
Authorization: Bearer {token}Returns:
{
"viewHistory": [...],
"interactions": [...]
}- id (UUID, Primary Key)
- email (Unique)
- username (Unique)
- password (Hashed)
- emailVerified (Boolean, Default: false)
- emailVerificationToken (String, nullable)
- emailVerificationTokenExpiry (DateTime, nullable)
- displayName (Optional)
- description (Optional)
- profilePictureUrl (Optional)
- lastEmailChange, lastUsernameChange
- createdAt, updatedAt
- id (UUID, Primary Key)
- title
- slug (URL-friendly version of title, unique per author)
- text (Markdown content)
- language (Optional tag)
- maxWidth (Default: 0)
- colorFreezeLevel (Default: 0)
- initialExpandLevel (Default: -1)
- isPublic (Default: true)
- authorId (Foreign Key to User, Optional)
- createdAt, updatedAt
- tags (Many-to-many relationship with Tag)
- Unique constraint: (authorId, slug)
- id (UUID, Primary Key)
- userId (Foreign Key to User, Optional)
- markmapId (Foreign Key to Markmap)
- viewedAt
- id (UUID, Primary Key)
- type (String: "expand", "collapse", "search", etc.)
- metadata (JSON)
- userId (Foreign Key to User, Optional)
- markmapId (Foreign Key to Markmap)
- interactedAt
- id (UUID, Primary Key)
- name (String, Unique - tag name with # prefix)
- createdAt, updatedAt
- id (UUID, Primary Key)
- tagId (Foreign Key to Tag)
- markmapId (Foreign Key to Markmap)
- createdAt
- Unique constraint on (tagId, markmapId)
- Framework: NestJS
- Database: PostgreSQL with Prisma ORM
- Authentication: JWT with Passport
- Validation: class-validator, class-transformer
- Password Hashing: bcrypt
- Email: Nodemailer
- Anti-bot: Cloudflare Turnstile
- Markmap: markmap-lib, markmap-view
- Visualization: Plotly.js (for tag analytics)
For quick production deployment, use the automated scripts:
# Initial deployment with domain and SSL
sudo ./scripts/deploy.sh --domain yourdomain.com
# Or skip SSL for manual setup later
sudo ./scripts/deploy.sh --domain yourdomain.com --skip-sslSee scripts/README.md for detailed script usage and options.
For detailed production deployment instructions, including:
- Ubuntu 24.04 LTS server setup
- PostgreSQL configuration
- Nginx reverse proxy setup
- SSL certificate with Let's Encrypt
- PM2 process management
- Email service configuration
- Security hardening
See the comprehensive docs/DEPLOYMENT-midiverse.md guide.
To update an existing deployment:
# Standard update
sudo ./scripts/update.sh
# Update with full restart
sudo ./scripts/update.sh --full-restart
# Dry run to see what would change
sudo ./scripts/update.sh --dry-runSee docs/MAINTENANCE-midiverse.md for manual maintenance procedures.
- Cloudflare Turnstile integration on signup and login forms
- Prevents automated account creation and brute force attacks
- Optional in development (gracefully skips if not configured)
- All new accounts require email verification
- Verification tokens expire after 24 hours
- Resend verification email functionality
- Prevents spam accounts and ensures valid contact information
- Bcrypt hashing with salt
- Minimum password length enforcement
- Secure password comparison
- JWT token-based authentication
- Token expiration (configurable, default 7 days)
- Protected endpoints require valid authentication