Skip to content

My (future, perpetually unfinished) personal portofolio website. Built with React, Express.js, and Elasticsearch, it features a powerful search engine, dynamic content management, interactive project galleries, and customizable themes. Showcases writings, projects, CV generation, and more!

License

Notifications You must be signed in to change notification settings

aldenluthfi/situsluthfi

Repository files navigation

πŸ“– About

A modern, full-stack personal portfolio website featuring a minimalistic design, powerful search capabilities, and comprehensive content management. Built with React, Express.js, and powered by Elasticsearch.

🌐 Live Website: aldenluth.fi

✨ Features

🎨 Frontend

  • Responsive Design: Mobile-first approach with tablet and desktop optimizations
  • Dark/Light Theme: Automatic timezone-based theme switching with manual override
  • Color Themes: 18 different color schemes to choose from
  • Interactive Maps: SVG-based world map for travel gallery
  • Custom Animations: Smooth transitions and micro-interactions
  • Accessibility: Full keyboard navigation and screen reader support
  • Progressive Web App: Offline capabilities and fast loading

πŸ” Search Engine

  • Elasticsearch Integration: Full-text search across all content
  • Smart Suggestions: Auto-complete with fuzzy matching
  • Multi-content Search: Search through writings, projects, and more
  • Real-time Results: Instant search with debounced queries

πŸ“ Content Management

  • Notion Integration: Automatic syncing of blog posts from Notion
  • Markdown Support: Full markdown rendering with custom components
  • Syntax Highlighting: Code blocks with multiple language support
  • Math Rendering: LaTeX support with KaTeX
  • Table of Contents: Auto-generated with smooth scrolling

πŸš€ Projects Showcase

  • GitHub Integration: Automatic repository syncing
  • Image Galleries: Project screenshots with zoom functionality
  • Technology Tags: Visual representation of tech stacks
  • Live Demos: Direct links to deployed projects

πŸ“Š Additional Features

  • CV Generation: Dynamic resume/CV with filtering
  • Gallery System: Photo galleries organized by location
  • Contact Forms: Integrated communication system
  • Analytics Ready: Easy integration with analytics platforms
  • Timezone Theming: Automatic theme switching based on my timezone (Asia/Jakarta)

πŸ› οΈ Tech Stack

Frontend

  • React 19 - UI library with latest features
  • TypeScript - Type-safe development
  • Vite - Fast build tool and dev server
  • Tailwind CSS - Utility-first styling
  • Framer Motion - Smooth animations
  • Radix UI - Accessible component primitives
  • React Router - Client-side routing

Backend

  • Node.js - JavaScript runtime
  • Express.js - Web application framework
  • TypeScript - Type-safe server development
  • MySQL - Relational database
  • Elasticsearch - Search engine
  • Notion API - Content management integration
  • GitHub API - Repository data fetching

DevOps & Deployment

  • Docker - Containerization
  • Kubernetes - Container orchestration
  • Kind - Local Kubernetes development
  • GitHub Actions - CI/CD pipeline
  • ESLint - Code linting
  • Husky - Git hooks

πŸš€ Quick Start

πŸ“– Prerequisites

  • Node.js 18+ and npm
  • Docker and Docker Compose
  • Kind (for Kubernetes deployment)
  • Git

βš™οΈ Development Setup

  1. Clone the repository

    git clone https://github.com/aldenluthfi/situsluthfi.git
    cd situsluthfi
  2. Install dependencies

    # Install root dependencies
    npm install
    
    # Install frontend dependencies
    cd frontend && npm install && cd ..
    
    # Install backend dependencies
    cd backend && npm install && cd ..
  3. Set up environment variables

    # Copy environment templates
    cp .env.example .env
    cp frontend/.env.example frontend/.env
    cp backend/.env.example backend/.env
    
    # Edit the .env files with your configuration
  4. Start development servers

    Option A: Using Docker Compose (Recommended)

    docker-compose up -d

    Option B: Manual setup

    # Start MySQL and Elasticsearch (using Docker)
    docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=yourpassword mysql:8.0
    docker run -d --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" elasticsearch:9.0.1
    
    # Start backend
    cd backend && npm run dev &
    
    # Start frontend
    cd frontend && npm run dev
  5. Access the application

    Option A: Using Docker Compose (Recommended)

    Option B: Manual setup

πŸ’Ό Production Deployment

Using Kubernetes with Kind:

./deploy.sh

Using Docker Compose:

docker-compose up -d

πŸ“ Project Structure

situsluthfi/
β”œβ”€β”€ frontend/                # React frontend application
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ components/      # Reusable UI components
β”‚   β”‚   β”œβ”€β”€ pages/           # Page components
β”‚   β”‚   β”œβ”€β”€ hooks/           # Custom React hooks
β”‚   β”‚   β”œβ”€β”€ lib/             # Utility functions
β”‚   β”‚   └── assets/          # Static assets
β”‚   β”œβ”€β”€ public/              # Public static files
β”‚   └── package.json
β”œβ”€β”€ backend/                 # Express.js backend API
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ routes/          # API route handlers
β”‚   β”‚   β”œβ”€β”€ db/              # Database configuration
β”‚   β”‚   └── utils/           # Utility functions
β”‚   β”œβ”€β”€ init.sql             # Database schema
β”‚   └── package.json
β”œβ”€β”€ k8s/                     # Kubernetes manifests
β”œβ”€β”€ .github/                 # GitHub Actions workflows
β”œβ”€β”€ docker-compose.yaml      # Development compose file
β”œβ”€β”€ deploy.sh                # Deployment script
└── README.md

πŸ”§ Configuration

Root .env:

MYSQL_USER=your_mysql_user
MYSQL_PASSWORD=your_mysql_password
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_DATABASE=your_database_name

Backend .env:

# Notion Integration
NOTION_API_KEY=your_notion_api_key
NOTION_WRITINGS_DATABASE_ID=your_database_id

# Database
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=your_user
MYSQL_PASSWORD=your_password
MYSQL_DATABASE=your_database

# Search Engine
ELASTICSEARCH_URL=http://localhost:9200

# GitHub Integration
GITHUB_TOKEN=your_github_token

Frontend .env:

VITE_EMAIL=your@email.com
VITE_INSTAGRAM=https://instagram.com/yourprofile
VITE_TWITTER=https://twitter.com/yourprofile
VITE_GITHUB=https://github.com/yourprofile
VITE_LINKEDIN=https://linkedin.com/in/yourprofile

πŸ“š API Documentation

The backend API provides comprehensive endpoints for managing content, search functionality, and data synchronization. All endpoints return JSON responses and follow RESTful conventions.

Base URL: http://host:3000/api

🎲 Facts API

Get Random Fact

Endpoint/facts
MethodGET
DescriptionRetrieve a random fact from the database
ParametersNone
Example RequestGET /api/facts
Example ResponsesStatus: 200 OK
{
  "id": 1,
  "text": "Honey never spoils",
  "source": "National Geographic"
}
Status: 500 Internal Server Error
{
  "error": "Failed to fetch facts"
}

✍️ Writings API

Get Paginated Writings

Endpoint/writings/get_page
MethodGET
DescriptionRetrieve paginated list of writings
Parameterspage (number, optional): Page number (default: 1)
pagesize (number, optional): Items per page (default: 10)
Example RequestGET /api/writings/get_page?page=1&pagesize=5
Example ResponsesStatus: 200 OK
{
  "results": [
    {
      "id": "abc123",
      "title": "My First Blog Post",
      "slug": "my-first-blog-post",
      "tags": ["technology", "web"],
      "lastUpdated": "2024-01-15T10:30:00Z",
      "createdAt": "2024-01-10T08:00:00Z"
    }
  ],
  "total": 25,
  "page": 1,
  "pageSize": 5,
  "totalPages": 5
}
Status: 500 Internal Server Error
{
  "error": "Failed to retrieve paginated writings"
}

Get Writing by Slug

Endpoint/writings/:slug
MethodGET
DescriptionRetrieve full writing content by slug
Parametersslug (string, required): URL-friendly identifier for the writing
Example RequestGET /api/writings/my-first-blog-post
Example ResponsesStatus: 200 OK
{
  "id": "abc123",
  "title": "My First Blog Post",
  "slug": "my-first-blog-post",
  "tags": ["technology", "web"],
  "content": "# My First Blog Post\n\nThis is the content...",
  "lastUpdated": "2024-01-15T10:30:00Z",
  "createdAt": "2024-01-10T08:00:00Z"
}
Status: 500 Internal Server Error
{
  "error": "Failed to retrieve writing"
}

Search Writing Contents

Endpoint/writings/search
MethodGET
DescriptionSearch through writing contents using Elasticsearch
Parametersq (string, required): Search query
page (number, optional): Page number (default: 1)
pagesize (number, optional): Items per page (default: 10)
Example RequestGET /api/writings/search?q=javascript&page=1&pagesize=5
Example ResponsesStatus: 200 OK
{
  "results": [
    {
      "id": "abc123",
      "title": "JavaScript Best Practices",
      "content": "...",
      "highlight": {
        "content": ["Learn JavaScript fundamentals"],
        "title": ["JavaScript Best Practices"]
      }
    }
  ],
  "total": {
    "value": 8,
    "relation": "eq"
  },
  "page": 1,
  "pageSize": 5,
  "totalPages": 2
}
Status: 400 Bad Request
{
  "error": "Missing search query"
}

Sync All Writings

Endpoint/writings/sync
MethodGET
DescriptionSynchronize all writings from Notion
ParametersNone
Example ResponsesStatus: 200 OK
{
  "message": "All writings synced successfully"
}
Status: 500 Internal Server Error
{
  "error": "Failed to sync all writings"
}

Sync Writing by Slug

Endpoint/writings/sync/:slug
MethodGET
DescriptionSynchronize specific writing content and index to Elasticsearch
Parametersslug (string, required): URL-friendly identifier for the writing
Example ResponsesStatus: 200 OK
{
  "message": "Writing content synced successfully"
}
Status: 500 Internal Server Error
{
  "error": "Failed to sync writing content"
}

πŸ™ GitHub API

Get User Repositories

Endpoint/github/repositories
MethodGET
DescriptionRetrieve all user repositories from database
ParametersNone
Example ResponsesStatus: 200 OK
{
  "count": 15,
  "repositories": [
    {
      "id": 123456,
      "name": "awesome-project",
      "description": "An awesome project built with React",
      "languages": {"JavaScript": 75, "CSS": 25},
      "stargazers_count": 42,
      "forks_count": 8,
      "topics": ["react", "frontend"],
      "created_at": "2024-01-01T00:00:00Z",
      "updated_at": "2024-01-15T12:00:00Z",
      "license": {"key": "mit", "name": "MIT License", "url": "https://...", "node_id": "...", "spdx_id": "MIT"},
      "html_url": "https://github.com/user/awesome-project",
      "readme": "# Awesome Project\n\nThis is awesome...",
      "cover_light_url": "https://raw.githubusercontent.com/.../light.png",
      "cover_dark_url": "https://raw.githubusercontent.com/.../dark.png",
      "icon_map": {"react": "https://cdn.jsdelivr.net/.../react.svg"}
    }
  ]
}
Status: 500 Internal Server Error
{
  "error": "Failed to fetch repositories"
}

Get Repository by Name

Endpoint/github/repositories/:name
MethodGET
DescriptionRetrieve specific repository by name
Parametersname (string, required): Repository name
Example RequestGET /api/github/repositories/awesome-project
Example ResponsesStatus: 200 OK
{
  "id": 123456,
  "name": "awesome-project",
  "description": "An awesome project built with React",
  "languages": {"JavaScript": 75, "CSS": 25},
  "stargazers_count": 42,
  "forks_count": 8,
  "topics": ["react", "frontend"],
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-15T12:00:00Z",
  "license": {"key": "mit", "name": "MIT License", "url": "https://...", "node_id": "...", "spdx_id": "MIT"},
  "html_url": "https://github.com/user/awesome-project",
  "readme": "# Awesome Project..."
}
Status: 404 Not Found
{
  "error": "Repository not found"
}
Status: 500 Internal Server Error
{
  "error": "Failed to fetch repository"
}

Sync Repositories

Endpoint/github/repositories/sync
MethodGET
DescriptionSynchronize repositories from GitHub API and index to Elasticsearch
ParametersNone
Example ResponsesStatus: 200 OK
{
  "message": "Repositories synced successfully"
}
Status: 500 Internal Server Error
{
  "error": "Failed to sync repositories"
}

πŸ” Search API

Universal Search

Endpoint/search
MethodGET
DescriptionSearch across all content types (writings and repositories)
Parametersq (string, required): Search query
page (number, optional): Page number (default: 1)
pagesize (number, optional): Items per page (default: 10)
Example RequestGET /api/search?q=react&page=1&pagesize=10
Example ResponsesStatus: 200 OK
{
  "results": [
    {
      "id": "abc123",
      "title": "React Best Practices",
      "content": "Learn React...",
      "_type": "writing",
      "highlight": {
        "title": ["React Best Practices"]
      }
    },
    {
      "id": 123456,
      "name": "react-components",
      "description": "Reusable React components",
      "_type": "repository",
      "highlight": {
        "name": ["react-components"]
      }
    }
  ],
  "total": {
    "value": 25,
    "relation": "eq"
  },
  "page": 1,
  "pageSize": 10,
  "totalPages": 3,
  "breakdown": {
    "writings": {"count": 15, "total": 25},
    "repositories": {"count": 10, "total": 25}
  }
}
Status: 400 Bad Request
{
  "error": "Search query is required"
}
Status: 500 Internal Server Error
{
  "error": "Failed to perform search"
}

Search Writings

Endpoint/search/writings
MethodGET
DescriptionSearch specifically within writings
Parametersq (string, required): Search query
page (number, optional): Page number (default: 1)
pagesize (number, optional): Items per page (default: 10)
Example RequestGET /api/search/writings?q=javascript&page=1&pagesize=5
Example ResponsesStatus: 200 OK
{
  "results": [
    {
      "id": "abc123",
      "title": "JavaScript Best Practices",
      "content": "...",
      "highlight": {
        "content": ["Learn JavaScript fundamentals"]
      }
    }
  ],
  "total": {
    "value": 8,
    "relation": "eq"
  },
  "page": 1,
  "pageSize": 5,
  "totalPages": 2
}
Status: 400 Bad Request
{
  "error": "Search query is required"
}
Status: 500 Internal Server Error
{
  "error": "Failed to search writings"
}

Search Repositories

Endpoint/search/repositories
MethodGET
DescriptionSearch specifically within repositories
Parametersq (string, required): Search query
page (number, optional): Page number (default: 1)
pagesize (number, optional): Items per page (default: 10)
Example RequestGET /api/search/repositories?q=react&page=1&pagesize=5
Example ResponsesStatus: 200 OK
{
  "results": [
    {
      "id": 123456,
      "name": "react-components",
      "description": "Reusable React components",
      "highlight": {
        "name": ["react-components"]
      }
    }
  ],
  "total": {
    "value": 5,
    "relation": "eq"
  },
  "page": 1,
  "pageSize": 5,
  "totalPages": 1
}
Status: 400 Bad Request
{
  "error": "Search query is required"
}
Status: 500 Internal Server Error
{
  "error": "Failed to search repositories"
}

πŸ“„ PDF API

Generate CV PDF

Endpoint/pdf/generate-cv
MethodPOST
DescriptionGenerate PDF from LaTeX content
Request BodylatexContent (string, required): LaTeX source code
filename (string, optional): Custom filename (default: "cv")
type (string, optional): CV type filter
mode (string, optional): Display mode
theme (string, optional): Color theme
Example Request
POST /api/pdf/generate-cv
Content-Type: application/json

{ "latexContent": "\documentclass{article} [...]", "filename": "my-cv", "type": "full", "mode": "system", "theme": "blue" }

Example ResponsesStatus: 200 OK
{
  "pdfUrl": "/api/pdf/view/my-cv-a1b2c3.pdf"
}
Status: 400 Bad Request
{
  "error": "LaTeX content is required"
}
Status: 422 Unprocessable Entity
{
  "error": "LaTeX compilation failed. Please check your content."
}

Serve PDF

Endpoint/pdf/view/:filename
MethodGET
DescriptionServe generated PDF file
Parametersfilename (string, required): PDF filename with extension
HeadersContent-Type: application/pdf
Content-Disposition: inline; filename="original-filename.pdf"
Content-Length: [buffer-length]
Example RequestGET /api/pdf/view/my-cv-a1b2c3.pdf
Example ResponsesStatus: 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="my-cv.pdf"
Content-Length: 50432

[Binary PDF data]

Status: 404 Not Found
{
  "error": "PDF not found"
}

πŸ“ž Contact

Feel free to reach out if you:

  • Have questions about the project or implementation
  • Want to collaborate on similar web development projects
  • Need help with the technologies used in this portfolio
  • Are interested in discussing potential opportunities

You can contact me via Email, hi@aldenluth.fi

βš–οΈ License

This repository is licensed under the GNU General Public License v3.0.

With this license, you are allowed to:

  • Use, copy, modify, and distribute the software
  • Create derivative works and commercial applications
  • Include the software in larger projects
  • Access and study the source code

However, you must:

  • Keep the same GPL v3.0 license for any derivative works
  • Provide source code for any distributed modifications
  • Include copyright and license notices
  • Document any changes made to the original code

πŸ™ Acknowledgments

About

My (future, perpetually unfinished) personal portofolio website. Built with React, Express.js, and Elasticsearch, it features a powerful search engine, dynamic content management, interactive project galleries, and customizable themes. Showcases writings, projects, CV generation, and more!

Topics

Resources

License

Stars

Watchers

Forks

Languages