# Configuration Guide This guide covers all configuration options for the Portfolio Website project, including environment variables, database settings, and application configuration. ## 📋 Environment Variables All configuration is managed through environment variables in the `.env` file. ### Core Configuration #### PostgreSQL Settings ```env # Prisma reads DATABASE_URL directly. DATABASE_URL=postgresql://aiyu:YOUR_PASSWORD@postgres:5432/aiyu?schema=public # Consumed by the Docker Compose Postgres service to provision the # role/database on first boot — must stay in sync with DATABASE_URL. POSTGRES_USER=aiyu POSTGRES_PASSWORD=YOUR_SECURE_PASSWORD POSTGRES_DB=aiyu POSTGRES_PORT=5432 # Optional pgAdmin (docker-compose-dev.yml only) PGADMIN_DEFAULT_EMAIL=admin@aiyu.local PGADMIN_DEFAULT_PASSWORD=YOUR_SECURE_PASSWORD PGADMIN_PORT=5050 ``` **Notes**: - Inside Docker Compose: use `postgres` as hostname - For host-based dev: use `localhost` - Password in `DATABASE_URL` must match `POSTGRES_PASSWORD` - Use strong, randomly generated passwords (24+ characters) - Connection pool size/timeout can be tuned via the `DATABASE_URL` query string, e.g. `...?connection_limit=10&pool_timeout=20` #### Admin Authentication ```env # Admin Panel Credentials ADMIN_USERNAME=admin ADMIN_PASSWORD=YourSecurePassword123! ``` **Requirements**: - Username: 4-20 characters, alphanumeric - Password: Minimum 12 characters, include uppercase, lowercase, numbers, special characters #### Security Tokens ```env # JWT Secret for authentication (64 characters recommended) JWT_SECRET=your_64_character_random_hex_string # Blog API Key for automated posting (32 characters recommended) BLOG_API_KEY=your_32_character_random_hex_string ``` **Generate Secure Tokens**: ```bash # JWT Secret node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" # Blog API Key node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ### SEO & Analytics ```env # Base URL (your domain or localhost for development) NEXT_PUBLIC_BASE_URL=https://yourdomain.com # Author name for SEO and meta tags NEXT_PUBLIC_AUTHOR_NAME=Your Full Name # Google Analytics ID (optional) NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX # Google Search Console verification (optional) NEXT_PUBLIC_GSC_VERIFICATION=your_verification_code ``` ### External Integrations #### n8n Webhook (Contact Form) ```env # n8n webhook URL for contact form submissions NEXT_PUBLIC_N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/contact ``` **Setup**: 1. Create an n8n workflow with a webhook trigger 2. Copy the webhook URL 3. Configure email notifications in n8n 4. Add URL to `.env` file #### AI Neural Core (Gemini / Groq / OpenRouter) Unlike the other integrations, AI provider keys are **not** set via `.env`. They're entered in the Admin Panel (Admin → AI Neural Core) and stored AES-256 encrypted in the `Config` table (`encryptedGeminiApiKey`, `encryptedGroqApiKey`, `encryptedOpenRouterApiKey`), decrypted in-memory only when a request needs them. **How to get a Gemini API Key**: 1. Visit [Google AI Studio](https://aistudio.google.com/) 2. Create or sign in to your account 3. Click "Get API key" 4. Paste it into Admin Panel → AI Neural Core → Gemini API Key #### GitHub Integration ```env # GitHub Personal Access Token — used as a fallback for repository # statistics if no token is configured in the admin panel (Admin → GitHub). GITHUB_TOKEN=ghp_your_personal_access_token ``` **How to get a GitHub Token**: 1. Go to GitHub Settings → Developer settings → Personal access tokens 2. Generate new token (classic) 3. Select scopes: `public_repo`, `read:user` 4. Either add it to `.env` as `GITHUB_TOKEN`, or save it via Admin Panel → GitHub (DB-stored token takes precedence) ### Docker Configuration ```env # Docker PostgreSQL Configuration (docker-compose.yml / docker-compose-dev.yml) POSTGRES_USER=aiyu POSTGRES_PASSWORD=YOUR_SECURE_PASSWORD POSTGRES_DB=aiyu POSTGRES_PORT=5432 ``` **Note**: These are read by the Postgres container to provision the role/database on first boot, and are interpolated into `DATABASE_URL` for the `app` service. ## 🗄️ Database Configuration ### Prisma Models The application uses PostgreSQL via Prisma (`prisma/schema.prisma`): - `Config` - Global configuration, theme settings, encrypted AI provider keys - `Project` / `Deployment` - Portfolio projects and live deployments - `Blog` - Blog posts and articles - `Gallery` - Gallery images and achievements - `ContactMessage` - Contact form submissions - `Theme` - Custom theme configurations - `About` / `Home` / `Header` - Singleton content blocks - `Social` - Social media links - `Cron` / `CronLog` / `CronEnv` - Task scheduler jobs, run history, encrypted secrets - `Ads`, `NotificationConfig`, `GitHub`, `AiLog` - Feature-specific settings/telemetry ### Database Indexes Defined directly in `prisma/schema.prisma`, e.g.: ```prisma // Blog @@index([published, createdAt]) @@index([slug]) // Project / Deployment @@index([displayOrder, year]) @@index([slug]) // ContactMessage @@index([read, createdAt]) @@index([email]) // Theme slug String @unique ``` ### Connection Pooling Prisma's connection pool is configured via the `DATABASE_URL` query string, not a separate config object: ```env DATABASE_URL=postgresql://aiyu:PASSWORD@postgres:5432/aiyu?schema=public&connection_limit=10&pool_timeout=20 ``` ## ⚙️ Application Configuration ### Global Config (Admin Panel) Access via Admin Panel → Config → Global Settings #### Site Information ```javascript { siteName: "Your Portfolio", siteDescription: "Portfolio website description", siteKeywords: "developer, portfolio, projects", siteAuthor: "Your Name", siteUrl: "https://yourdomain.com" } ``` #### SEO Settings ```javascript { seoTitle: "Your Name | Portfolio", seoDescription: "Professional portfolio showcasing projects...", seoKeywords: ["web developer", "full stack", "react"], ogImage: "/images/og-image.jpg", twitterHandle: "@yourusername" } ``` #### Analytics ```javascript { googleAnalyticsId: "G-XXXXXXXXXX", enableAnalytics: true, trackOutboundLinks: true } ``` #### Feature Flags ```javascript { enableBlog: true, enableGallery: true, enableContactForm: true, enableGitHubIntegration: true, openToWork: true } ``` ### Header Configuration Access via Admin Panel → Config → Header ```javascript { logo: { text: "Aiyu", image: "/logo.png" // optional }, navigation: [ { label: "Home", href: "/" }, { label: "About", href: "/about" }, { label: "Projects", href: "/projects" }, { label: "Blog", href: "/blog" }, { label: "Gallery", href: "/gallery" }, { label: "Contact", href: "/contact" } ], showThemeToggle: true } ``` ### Footer Configuration Access via Admin Panel → Config → Footer ```javascript { copyrightText: "© 2025 Your Name. All rights reserved.", version: "v1.0.0", socialLinks: [ { platform: "github", url: "https://github.com/yourusername" }, { platform: "linkedin", url: "https://linkedin.com/in/yourusername" }, { platform: "twitter", url: "https://twitter.com/yourusername" } ], footerLinks: [ { label: "Privacy Policy", href: "/privacy" }, { label: "Terms of Service", href: "/terms" } ] } ``` ## 🎨 Theme Configuration ### Built-in Themes 52 pre-built themes available: 1. VS Code Dark 2. Ocean Blue 3. Forest Green 4. Sunset Orange 5. Royal Purple 6. Monochrome 7. Dracula 8. Nord 9. Cyberpunk 10. Gruvbox 11. Solarized 12. Catppuccin 13. Tokyo Night 14. Material Ocean 15. Synthwave 16. Forest 17. Sunset 18. Aurora 19. Coral Reef 20. Espresso 21. Midnight Blue 22. Cyber Indigo 23. Matcha Latte 24. Nordic Frost 25. Sakura Dream 26. Neo Brutalist 27. Luxury Champagne 28. Oceanic Abyss 29. Solar Flare 30. Lavender Haze 31. Glassmorphic Neon 32. Terracotta Clay 33. Obsidian Prism 34. Rosemary Mint 35. Sunset Boulevard 36. Monolithic Slate 37. Desert Mirage 38. Crimson Velvet 39. Cyber Matcha 40. Mocha Mousse 41. Peach Fuzz 42. Electric Citrus 43. Bubblegum Pop 44. Emerald Noir 45. Cotton Candy Sky 46. Terminal Matrix 47. Copper Patina 48. Nebula Drift 49. Royal Sapphire 50. Ink & Vermilion 51. Acid Graphite 52. Firefly Grove ### Custom Theme Structure ```javascript { name: "Custom Theme", colors: { primary: "#3B82F6", secondary: "#8B5CF6", accent: "#F59E0B", background: "#0F172A", surface: "#1E293B", text: "#F1F5F9", textSecondary: "#94A3B8", border: "#334155", success: "#10B981", warning: "#F59E0B", error: "#EF4444" }, fonts: { heading: "Inter, sans-serif", body: "Inter, sans-serif", mono: "Fira Code, monospace" }, spacing: { unit: "4px" }, borderRadius: { sm: "4px", md: "8px", lg: "12px", xl: "16px" } } ``` ## 🔧 Next.js Configuration ### next.config.mjs (actual, abridged) ```javascript const nextConfig = { output: 'standalone', // disabled automatically on Windows / NEXT_DISABLE_STANDALONE=true experimental: { optimizeCss: true, optimizePackageImports: ['framer-motion', 'lucide-react', 'react-icons', 'simple-icons', 'lodash-es', 'date-fns-tz'], proxyClientMaxBodySize: '999mb', // backups with many images can exceed the 10MB default }, images: { unoptimized: false, // Sharp is installed, so optimization stays on remotePatterns: [ { protocol: 'http', hostname: 'localhost' }, { protocol: 'https', hostname: 'avatars.githubusercontent.com' }, { protocol: 'https', hostname: 'raw.githubusercontent.com' }, // + NEXT_PUBLIC_CDN_URL hostname, if set ], formats: ['image/webp', 'image/avif'], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, compress: true, poweredByHeader: false, }; ``` Add additional remote image hosts via `remotePatterns` directly in `next.config.mjs` if you embed images from elsewhere. ## 🐳 Docker Configuration ### docker-compose.yml (production, abridged — see the real file for the full version) ```yaml services: postgres: image: postgres:17-alpine environment: POSTGRES_USER: ${POSTGRES_USER:-aiyu} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB:-aiyu} volumes: - postgres_data:/var/lib/postgresql/data app: image: aiyuayaan/aiyu:${APP_IMAGE_TAG:-latest} environment: DATABASE_URL: postgresql://${POSTGRES_USER:-aiyu}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-aiyu}?schema=public RUN_MIGRATIONS: "true" JWT_SECRET: ${JWT_SECRET} ADMIN_USERNAME: ${ADMIN_USERNAME} ADMIN_PASSWORD: ${ADMIN_PASSWORD} security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - NET_BIND_SERVICE read_only: true tmpfs: - /tmp:noexec,nosuid,nodev,mode=1777 nginx: image: nginx:alpine ports: - "${NGINX_HTTP_PORT:-80}:80" volumes: postgres_data: ``` > Use `docker-compose-local.yml` instead if you want the app built from > source (`build: { context: ., dockerfile: Dockerfile }`) rather than > pulling the published `aiyuayaan/aiyu` image. ## 📱 Image Optimization ### Sharp Configuration (as used in the AI captioning route) ```javascript sharp(buffer) .resize(1024, 1024, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: 80 }) .toBuffer(); ``` `next/image` itself handles general page-image optimization automatically (WebP/AVIF, responsive `deviceSizes`/`imageSizes` from `next.config.mjs`). ### Upload Limits (`src/utils/fileValidation.js`) ```javascript export const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB export const ALLOWED_MIME_TYPES = [ 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/heic', 'image/heif', ]; ``` Validation checks the file's magic bytes, not just its extension/MIME header — SVG is intentionally excluded (XSS risk via inline `