Skip to content

ZanDev32/PlaceRadar

Repository files navigation


Logo

Lokasi Tepat, Produktivitas Hebat!

About Us · Vote Project Now · Report Bug

PWA Badge

Introduction

PlaceRadar is a hyper-specific workspace discovery platform for students and remote workers in Sleman, Yogyakarta. It highlights practical amenities such as Wi-Fi stability, outlet availability, and ambient noise so users can quickly choose the best "third place" for focus work.

Table of Contents

Architecture Overview

The application runs as six Docker services defined in docker-compose.yml:

Architecture

Service Container Description
Frontend ReactJS React 19 + Vite SPA
Backend ExpressJS Node.js REST API
Database PostgreSQL Relational database
Reverse Proxy nginx TLS termination & routing
Monitoring NagiOS Service health checks
DNS CoreDNS Local domain resolution

Tech Stack

Project Structure

PlaceRadar/
├── backend/                 # Express.js REST API
│   ├── src/
│   │   ├── controllers/     # HTTP request handlers
│   │   ├── models/          # Sequelize models
│   │   ├── routes/          # API route definitions
│   │   ├── services/        # Business logic
│   │   ├── config/          # Database & environment config
│   │   └── middleware/      # Error handling
│   └── Dockerfile
├── frontend/                # React SPA
│   ├── src/
│   │   ├── components/      # Reusable UI components
│   │   ├── context/         # React Context providers
│   │   ├── hooks/           # Custom hooks
│   │   ├── pages/           # Route pages
│   │   └── styles/          # Global CSS
│   └── Dockerfile
├── infrastructure/
│   ├── nginx/               # Reverse proxy config + TLS certs
│   ├── coredns/             # DNS server config
│   ├── nagios/              # Monitoring config
│   └── ngrok/               # Tunnel config (optional)
├── scripts/seed/            # Sample data
├── docker-compose.yml
└── .env.example

Prerequisites

  • Docker Desktop ≥ 24
  • Docker Compose v2
  • Node.js 18+ (only for local development without Docker)

Getting Started

  1. Clone the repository

    git clone https://github.com/ZanDev32/PlaceRadar.git
    cd PlaceRadar
  2. Copy environment template

    cp .env.example .env
  3. Launch the full stack

    docker compose up -d --build
  4. Verify all containers are running

    docker ps

Accessing the Application

The main address for the app is placeradar.lvh.me (recommended). This is a "magic" domain that automatically resolves to 127.0.0.1 on any OS without configuration.

Service URL Notes
Frontend (HTTPS) https://placeradar.lvh.me Accept self-signed cert warning
Frontend (HTTP) http://placeradar.lvh.me Served over HTTP (no redirect)
API https://placeradar.lvh.me/api/place REST endpoints
Nagios https://placeradar.lvh.me/nagios Monitoring dashboard
pgAdmin https://placeradar.lvh.me/pgadmin Database UI

Environment Configuration

Edit .env to configure the application. Key variables:

Variable Default Description
DATABASE_URL postgres://postgres:postgres@postgres:5432/placeradar PostgreSQL connection string
POSTGRES_USER postgres PostgreSQL user created on first init
POSTGRES_PASSWORD postgres PostgreSQL password created on first init
POSTGRES_DB placeradar PostgreSQL database created on first init
PGADMIN_DEFAULT_EMAIL [email protected] Initial pgAdmin login (first init only)
PGADMIN_DEFAULT_PASSWORD admin123 Initial pgAdmin password (first init only)
NAGIOS_USERNAME nagiosadmin Nagios web UI username
NAGIOS_PASSWORD admin123 Nagios web UI password
NGROK_AUTH_TOKEN - Ngrok authentication (optional)
JWT_SECRET your_jwt_secret Secret key for signing JWT tokens
JWT_EXPIRES_IN 1h JWT expiration, e.g., 15m, 1h, 7d
ADMIN_USERNAME admin Admin login for JWT issuance
ADMIN_PASSWORD admin123 Admin password for JWT issuance
VITE_MAPBOX_TOKEN - Mapbox access token (required for frontend maps)
GOOGLE_CLIENT_ID - Google OAuth Client ID (Backend verification)
VITE_GOOGLE_CLIENT_ID - Google OAuth Client ID (Frontend widget)
OPENAI_API_KEY - Backend-only key for AI review summaries (server-side, do not expose to frontend)
OPENAI_MODEL gpt-4o-mini OpenAI model used for summaries
OPENAI_BASE_URL https://api.openai.com/v1 Override when using a compatible gateway
R2_ACCOUNT_ID - Cloudflare Account ID
R2_ACCESS_KEY_ID - Cloudflare R2 Access Key ID
R2_SECRET_ACCESS_KEY - Cloudflare R2 Secret Access Key
R2_BUCKET_NAME placeradar-images Name of the R2 bucket
R2_PUBLIC_URL - Public URL for accessing images (Backend)
VITE_R2_PUBLIC_URL - Public URL for accessing images (Frontend)

The backend reads configuration from backend/src/config/env.js. Review summaries call OpenAI from the backend only; keep OPENAI_API_KEY out of frontend env files.

Image Handling

Images are stored in Cloudflare R2 (S3-compatible storage) and accessed via a public URL.

Backend Storage

  • Uploads: Handled via multer (memory storage) and processed with sharp (converted to WebP).
  • Storage Service: backend/src/services/storage.service.js manages S3 interactions.
  • Database: Stores the relative path (key) of the image, e.g., Place_image/Cafe Name/1.webp.

Frontend Display

To display images in the frontend, you must construct the full URL using the VITE_R2_PUBLIC_URL environment variable.

Helper Function Example:

const buildImageUrl = (imagePath) => {
    if (!imagePath) return null;
    // If it's already a full URL, return it
    if (/^https?:\/\//i.test(imagePath)) return imagePath;

    // Get base URL from env, remove trailing slash
    const base = import.meta.env.VITE_R2_PUBLIC_URL?.replace(/\/$/, '') ?? '';
    
    // Encode path segments to handle spaces and special characters
    const encodedPath = imagePath
        .split('/')
        .map((segment) => encodeURIComponent(segment))
        .join('/');

    return base ? `${base}/${encodedPath}` : encodedPath;
};

Usage in Component:

const imageUrl = buildImageUrl(location.images[0]);

return (
    <img src={imageUrl} alt={location.name} />
);

Important: Ensure VITE_R2_PUBLIC_URL is set in your .env file and passed to the frontend container (via docker-compose.yml and Dockerfile).

Authentication

The application supports both traditional username/password authentication and Google OAuth.

User Roles

  • User: Can register, login, and view their profile.
  • Admin: Has additional privileges (e.g., managing locations).

Endpoints

  • Register: POST /api/auth/register
    • Body: { "username": "...", "email": "...", "password": "..." }
  • Login: POST /api/auth/login
    • Body: { "username": "...", "password": "..." }
  • Google Login: POST /api/auth/google
    • Body: { "token": "<google_id_token>" }
  • Get Profile: GET /api/auth/me
    • Header: Authorization: Bearer <jwt>
  • Update Profile: PATCH /api/auth/me
    • Header: Authorization: Bearer <jwt>
    • Body: { "firstName": "...", "lastName": "...", "bio": "...", "preferences": [...] }
  • Upload Avatar: POST /api/auth/me/avatar
    • Header: Authorization: Bearer <jwt>
    • Body: FormData with image file

Admin Access

The system supports a bootstrapped admin user via environment variables (ADMIN_USERNAME, ADMIN_PASSWORD). This user has the admin role and can be used for initial setup.

Credentials persistence (important)

PostgreSQL and pgAdmin both store credentials in Docker volumes on first initialization. Changing .env values later will update container environment variables, but it will not automatically rewrite existing users/passwords.

  • pgAdmin: PGADMIN_DEFAULT_EMAIL/PGADMIN_DEFAULT_PASSWORD only apply on first init.
    • To change the credential without deleting data:
      docker compose stop pgadmin
      docker compose rm -f pgadmin
      docker volume rm placeradar_pgadmin-data
      docker compose up -d pgadmin
  • PostgreSQL: POSTGRES_USER/POSTGRES_PASSWORD only applies on first init.
    • To change the credential without deleting data:
      docker compose down postgres
      docker volume rm placeradar_postgres-data
      docker compose up -d postgres

Reviews

User-submitted comments for locations (no rating system). Reviews cannot be edited; authors can delete their own review.

Endpoints

  • POST /api/reviews/:placeId (auth) — body: { "comment": "...", "images": ["..."] }
  • GET /api/reviews/:placeId — list all reviews for a location, newest first
  • DELETE /api/reviews/:reviewId (auth, owner) — remove a review and decrement location review_count

AI Review Summary

  • POST /api/place/:placeId/review-summary/generate — start summary generation (returns queued/running immediately)
  • GET /api/place/:placeId/review-summary — poll status/results; returns summary text, status, and errors if any
  • Regenerates automatically when the location review_count changes; shows loading/error states in the UI.

Development Workflow

Frontend (React + Vite)

cd frontend
npm install
npm run dev      # Development server with HMR
npm run build    # Production build
npm run serve    # Preview production build

Backend (Express.js)

cd backend
npm install
npm run dev      # Development with nodemon
npm start        # Production mode
npm test         # Run Jest tests

Full Stack (Docker)

docker compose up -d --build    # Build and start all services
docker compose logs -f          # Follow logs
docker compose down             # Stop all services
docker compose down -v          # Stop and remove volumes

Seeding Sample Data

Quick start (recommended):

./scripts/seed/run_all_seeds.sh

Step-by-step seeding order:

  1. Build/rebuild the backend container (if not done yet):
docker compose build backend
  1. Run SQL seeds first (creates PostGIS extensions/schema and helper data):
./scripts/seed/run_sql_seeds.sh
  1. Seed app locations from JSON (after SQL seeds):
docker exec -it ExpressJS npm run seed:locations
  1. Seed sample users (optional):
docker exec -it ExpressJS npm run seed:users
  1. Seed sample reviews (optional, requires locations + users):
docker exec -it ExpressJS npm run seed:reviews

Alternative: run all backend JSON seeders in one command (after SQL seeds):

docker exec -it ExpressJS npm run seed:all

Notes and troubleshooting:

  • SQL seeds assume the postgres container name; override POSTGRES_USER/POSTGRES_DB env vars if your setup differs.
  • The location seeder is fail-fast: it validates the JSON and stops on the first error.
  • It runs in sync mode: upserts rows from scripts/seed/locations.json and deletes any Location rows not in the file.
  • The review seeder prefers scripts/seed/reviews.json (recommended). Format per entry:
    • locationId (UUID from scripts/seed/locations.json)
    • userEmail (must exist in scripts/seed/users.json)
    • comment (string)
    • images (array, typically [])
    • If scripts/seed/reviews.json is missing, it falls back to generating one default review per location.
  • DB connection:
    • In Docker, ensure the postgres container is running (e.g., docker compose up -d postgres or the full stack). The hostname postgres must resolve inside the network.
    • Outside Docker/local DB, set DATABASE_URL in backend/.env to your local instance, e.g. postgres://user:pass@localhost:5432/placeradar, then rerun npm run seed:locations.

Promoted ads (dummy data):

  • The promoted cards endpoint reads from scripts/seed/ads_dummy.json.
  • The backend serves ad image files from /api/static/ads/<filename>.
    • In Docker, this is wired by volume-mounting frontend/src/assets/ads into the backend container.

Key Application Modules

Backend

Module Path Description
Location Model backend/src/models/Location.js Sequelize model for workspace locations
User Model backend/src/models/User.js Sequelize model for users
Review Model backend/src/models/Review.js Sequelize model for location reviews (comments + images, no ratings)
Location Service backend/src/services/locations.service.js CRUD business logic
Location Controller backend/src/controllers/locations.controller.js HTTP request handlers for locations
Reviews Controller backend/src/controllers/reviews.controller.js Create, list, delete reviews
Auth Controller backend/src/controllers/auth.controller.js Authentication logic (Login, Register, Google)
Location Routes backend/src/routes/locations.routes.js Location endpoint definitions
Reviews Routes backend/src/routes/reviews.routes.js Review endpoint definitions

Frontend

Module Path Description
Location Context frontend/src/context/LocationContext.jsx Global state management
useApi Hook frontend/src/hooks/useApi.js API data fetching
Home Page frontend/src/pages/Home.jsx Location listing
Login Page frontend/src/pages/Login.jsx User authentication page

Infrastructure Services

Nginx (Reverse Proxy)

  • Terminates TLS on port 443
  • Serves React static files from /
  • Proxies /api requests to the Express backend
  • Redirects HTTP → HTTPS

CoreDNS

Resolves local development domains. Configuration: infrastructure/coredns/Corefile

placeradar.lvh.me     → 127.0.0.1
api.placeradar.lvh.me → 127.0.0.1

To use CoreDNS, set your OS DNS server to 127.0.0.1.

Nagios

Monitors service health. Access at https://placeradar.lvh.me/nagios

Ngrok (Optional)

Expose local development externally. Set NGROK_AUTH_TOKEN in .env.

TLS Certificate Setup

The project includes a self-signed certificate for HTTPS development. Browsers will show a warning which you can bypass.

To regenerate the certificate:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout infrastructure/nginx/certs/placeradar.lvh.me.key \
  -out infrastructure/nginx/certs/placeradar.lvh.me.crt \
  -subj "/CN=placeradar.lvh.me" \
  -addext "subjectAltName=DNS:placeradar.lvh.me,DNS:localhost"

To trust the certificate (optional):

  • Firefox: about:preferences#privacy → View Certificates → Authorities → Import
  • Chrome/System: Add to OS trust store
  • Alternative: Use mkcert for locally-trusted certificates

License

© 2025 PlaceRadar. All rights reserved.