No description
  • TypeScript 92.9%
  • JavaScript 3.3%
  • MDX 2.5%
  • CSS 0.8%
  • Dockerfile 0.5%
Find a file
dependabot[bot] 4d8f75ffd5
build(deps): Bump the minor-and-patch group with 6 updates (#12)
Bumps the minor-and-patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [fuse.js](https://github.com/krisk/Fuse) | `7.3.0` | `7.4.0` |
| [js-cookie](https://github.com/js-cookie/js-cookie) | `3.0.7` | `3.0.8` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.1.1` | `4.2.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.16.0` | `1.17.0` |
| [resend](https://github.com/resend/resend-node) | `6.12.3` | `6.12.4` |
| [tsx](https://github.com/privatenumber/tsx) | `4.22.3` | `4.22.4` |


Updates `fuse.js` from 7.3.0 to 7.4.0
- [Release notes](https://github.com/krisk/Fuse/releases)
- [Changelog](https://github.com/krisk/Fuse/blob/main/CHANGELOG.md)
- [Commits](https://github.com/krisk/Fuse/compare/v7.3.0...v7.4.0)

Updates `js-cookie` from 3.0.7 to 3.0.8
- [Release notes](https://github.com/js-cookie/js-cookie/releases)
- [Commits](https://github.com/js-cookie/js-cookie/compare/v3.0.7...v3.0.8)

Updates `js-yaml` from 4.1.1 to 4.2.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/commits)

Updates `lucide-react` from 1.16.0 to 1.17.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.17.0/packages/lucide-react)

Updates `resend` from 6.12.3 to 6.12.4
- [Release notes](https://github.com/resend/resend-node/releases)
- [Commits](https://github.com/resend/resend-node/compare/v6.12.3...v6.12.4)

Updates `tsx` from 4.22.3 to 4.22.4
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.3...v4.22.4)

---
updated-dependencies:
- dependency-name: fuse.js
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: js-cookie
  dependency-version: 3.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: lucide-react
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: resend
  dependency-version: 6.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: tsx
  dependency-version: 4.22.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-31 23:10:56 +00:00
.github fix(spotify): use configurable redirect URI for OAuth 2026-05-03 19:29:36 +02:00
config feat: Add privacy-respecting analytics and Playwright E2E tests 2026-05-03 16:42:53 +02:00
content feat: add batch 1 features (stats, timeline, related posts, education) 2026-05-03 17:47:13 +02:00
docs docs: add Spotify environment variables to Docker 2026-05-03 18:43:31 +02:00
drizzle feat: centralized tag system with admin management 2026-05-03 13:48:47 +02:00
public feat: add RSS feed, ToC, Now/Uses/Homelab pages, and legal pages 2026-05-03 16:13:15 +02:00
scripts feat(spotify): support production OAuth in helper script 2026-05-03 19:31:15 +02:00
src style: widen about page layout 2026-05-03 19:58:40 +02:00
.dockerignore feat: add Docker support and GitHub Actions CI/CD 2026-05-03 10:19:15 +02:00
.env.example fix(spotify): use configurable redirect URI for OAuth 2026-05-03 19:29:36 +02:00
.gitignore chore: add Spotify and Stats.fm API configuration 2026-05-03 17:48:34 +02:00
.nvmrc chore: upgrade to Node.js 24 2026-05-03 10:24:59 +02:00
AGENTS.md Initial commit from Create Next App 2026-05-02 20:25:53 +02:00
CLAUDE.md Initial commit from Create Next App 2026-05-02 20:25:53 +02:00
docker-compose.yml fix(spotify): use configurable redirect URI for OAuth 2026-05-03 19:29:36 +02:00
Dockerfile chore: reduce Docker install warnings and simplify release tags 2026-05-03 11:14:41 +02:00
drizzle.config.ts feat: SQLite admin auth — DB schema, sessions, password hashing, API routes 2026-05-03 09:40:17 +02:00
eslint.config.mjs fix: resolve all ESLint errors and warnings 2026-05-03 10:37:55 +02:00
LICENSE Add MIT License to the project 2026-05-03 00:09:48 +02:00
middleware.ts feat: SQLite admin auth — DB schema, sessions, password hashing, API routes 2026-05-03 09:40:17 +02:00
next.config.ts feat(spotify): add Spotify widgets and music page 2026-05-03 19:52:50 +02:00
nextjs_admin_prompt.md feat: SQLite admin auth — DB schema, sessions, password hashing, API routes 2026-05-03 09:40:17 +02:00
package-lock.json build(deps): Bump the minor-and-patch group with 6 updates (#12) 2026-05-31 23:10:56 +00:00
package.json build(deps): Bump the minor-and-patch group with 6 updates (#12) 2026-05-31 23:10:56 +00:00
postcss.config.mjs Initial commit from Create Next App 2026-05-02 20:25:53 +02:00
README.md feat(spotify): support production OAuth in helper script 2026-05-03 19:31:15 +02:00
ROADMAP.md refactor: Remove unnecessary complexity and organize ideas 2026-05-03 17:00:04 +02:00
tsconfig.json Initial commit from Create Next App 2026-05-02 20:25:53 +02:00

Portfolio

A modern, self-hosted portfolio website built with Next.js 16, featuring a full admin dashboard, MDX-based content management, and comprehensive SEO optimization.

CI Docker Pulls License: MIT

Features

🎨 Public Portfolio

  • Responsive Design — Mobile-first, accessible UI with dark mode support
  • MDX-Powered Content — Write blog posts, projects, and experiences in Markdown with React components
  • Advanced Search — Fuzzy search across all content with keyboard shortcuts (Cmd/Ctrl+K)
  • SEO Optimized — OpenGraph images, JSON-LD structured data, dynamic sitemap, robots.txt
  • GitHub Integration — Contribution calendar visualization
  • Contact Form — Email submissions with file attachments via Resend API

🛠️ Admin Dashboard (/admin)

  • Content Management — Create, edit, and manage all content types through a visual editor
  • Markdown Editor — Live preview with syntax highlighting and drag-and-drop image upload
  • Database Viewer — SQL editor and table inspector for SQLite database
  • Session Management — View and revoke active admin sessions
  • SSO/OIDC Support — Configure multiple identity providers (Google, Microsoft, Okta, PocketID, Authentik, Keycloak, etc.)
  • OpenAPI Documentation — Interactive API docs for all admin endpoints

🔐 Authentication

  • Local Auth — Argon2-based password hashing, session cookies
  • SSO/OIDC — Database-backed provider configuration with env var fallback
  • Session Management — SQLite-backed sessions with configurable expiry

🐳 Production-Ready

  • Docker — Optimized amd64 images on Docker Hub and GitHub Container Registry
  • CI/CD — Automated linting, type-checking, builds, and releases via GitHub Actions
  • CodeQL Security Scanning — Automated vulnerability detection
  • Dependabot — Auto-merge for minor/patch dependency updates

🚀 Quick Start

# Pull the latest image
docker pull ftmahringer/portfolio:latest

# Run with required environment variables
docker run -d \
  -p 3000:3000 \
  -v portfolio-data:/app/data \
  -e ADMIN_EMAIL=admin@example.com \
  -e ADMIN_PASSWORD=secure-password \
  -e API_SECRET=random-secret-key \
  --name portfolio \
  ftmahringer/portfolio:latest

Or use docker-compose.yml:

version: '3.8'
services:
  portfolio:
    image: ftmahringer/portfolio:latest
    ports:
      - "3000:3000"
    environment:
      ADMIN_EMAIL: admin@example.com
      ADMIN_PASSWORD: secure-password
      API_SECRET: random-secret-key
      # Optional: Spotify Integration
      # SPOTIFY_CLIENT_ID: your-spotify-client-id
      # SPOTIFY_CLIENT_SECRET: your-spotify-client-secret
      # SPOTIFY_REFRESH_TOKEN: your-spotify-refresh-token
      # SPOTIFY_REDIRECT_URI: https://your-domain.com/api/spotify/callback
      # Optional: SSO via env vars
      # OIDC_ISSUER: https://accounts.google.com
      # OIDC_CLIENT_ID: your-client-id
      # OIDC_CLIENT_SECRET: your-client-secret
    volumes:
      - portfolio-data:/app/data
    restart: unless-stopped

volumes:
  portfolio-data:

Option 2: Local Development

# Clone the repository
git clone https://github.com/FTMahringer/Portfolio.git
cd Portfolio

# Install dependencies
npm install

# Set up environment variables
cp .env.local.example .env.local
# Edit .env.local with your values

# Initialize the database
npm run db:push

# Run the development server
npm run dev

Visit http://localhost:3000 for the public site and http://localhost:3000/admin for the admin dashboard.

📋 Environment Variables

Required

Variable Description Example
ADMIN_EMAIL Default admin email (auto-seeded on first start) admin@example.com
ADMIN_PASSWORD Default admin password secure-password
API_SECRET Secret for API authentication random-secret-key

Optional: Spotify Integration

To enable the Spotify "Now Playing" widget, configure the following environment variables:

Variable Description Example
SPOTIFY_CLIENT_ID Spotify app client ID 829beffbf6fe...
SPOTIFY_CLIENT_SECRET Spotify app client secret 19c278a670...
SPOTIFY_REFRESH_TOKEN OAuth refresh token Get via authorization flow (see below)
SPOTIFY_REDIRECT_URI Public OAuth callback URL https://portfolio.ftmahringer.com/api/spotify/callback

Getting the Spotify refresh token:

  1. Create a Spotify app at https://developer.spotify.com/dashboard
  2. Add redirect URI: https://your-domain.com/api/spotify/callback
  3. Set SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, and SPOTIFY_REDIRECT_URI in your environment
  4. Visit this URL (replace YOUR_CLIENT_ID and use the same domain as SPOTIFY_REDIRECT_URI):
    https://accounts.spotify.com/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https://your-domain.com/api/spotify/callback&scope=user-read-currently-playing+user-read-recently-played+user-top-read
    
  5. Authorize the app
  6. Copy the refresh token from the success page
  7. Add it to your environment as SPOTIFY_REFRESH_TOKEN

Important: The SPOTIFY_REDIRECT_URI must match exactly what's configured in your Spotify app settings. Use your public domain (e.g., https://portfolio.ftmahringer.com/api/spotify/callback), not internal/VPN domains.

Note: If these variables are not set, Spotify features will be automatically disabled.

Optional: Analytics

Variable Description Example
NEXT_PUBLIC_UMAMI_WEBSITE_ID Umami website ID abc-123-def
NEXT_PUBLIC_UMAMI_URL Umami analytics server URL https://analytics.example.com
NEXT_PUBLIC_PLAUSIBLE_DOMAIN Plausible domain your-domain.com

Analytics are only loaded in production and require configuration in config/site.yaml.

Optional: Other

Variable Description Default
DATABASE_URL SQLite database path file:./data/portfolio.db
NEXT_PUBLIC_BASE_URL Public URL for OG images and canonical links http://localhost:3000
RESEND_API_KEY API key for email submissions -
CONTACT_EMAIL Email address to receive contact form submissions -
OIDC_ISSUER OIDC provider issuer URL (env var fallback) -
OIDC_CLIENT_ID OIDC client ID (env var fallback) -
OIDC_CLIENT_SECRET OIDC client secret (env var fallback) -

🐳 Docker Tags

We publish amd64 images to both Docker Hub and GitHub Container Registry:

Tag Description Example
latest Latest stable release ftmahringer/portfolio:latest
X.Y.Z Exact version ftmahringer/portfolio:2.1.0
X.Y Latest patch in X.Y.* ftmahringer/portfolio:2.1
X Latest minor/patch in X.* ftmahringer/portfolio:2

Recommendation: Use X.Y for production (e.g., 2.1) to get patch updates automatically while avoiding breaking changes.

🛠️ Tech Stack

🐛 Troubleshooting

Port 3000 already in use

# Change port in docker run
docker run -p 8080:3000 ...

# Or in docker-compose.yml
ports:
  - "8080:3000"

Database locked errors

The SQLite database is in /app/data/portfolio.db inside the container. Make sure it''s persisted in a volume and not shared across multiple container instances.

Admin login not working

If you changed ADMIN_EMAIL or ADMIN_PASSWORD after the first start, the admin user was already seeded. Either:

  1. Delete the volume and restart (resets database), or
  2. Use the existing credentials, or
  3. Manually update the password via SQL: npm run db:studio → users table

📄 License

MIT — Feel free to use this template for your own portfolio!


Need help? Open an issue on GitHub.