Skip to main content

Main Website CMS - Documentation

πŸ”Ž Background

Overview​

This project is the Content Management System (CMS) for RISTEK's Main Website, built with Next.js, NextAuth.js for authentication, RTK Query for client-side data fetching, and shadcn/ui for most of the components.

Stakeholders​

  • Users: UI students and staff (monitoring real-time bus locations), Bikun Administrators (accessing reporting dashboards and schedules).
  • Upstream Dependencies: Bikun Tracker Backend V2 (providing REST API endpoints and /ws WebSocket), UI SSO (for authentication).
  • Downstream Consumers: End-user devices (Mobile and desktop web browsers).

Scope & Boundaries​

In Scope

  • Real-time interactive map rendering using Leaflet.
  • Maintaining continuous WebSocket connections to receive live bus GPS coordinates.
  • User interface for bus schedule management and reporting.
  • Client-side authentication integration utilizing UI CAS SSO.
  • Local state management for bus movement interpolation.

Out of Scope

  • Heavy computational processing for lane detection or lap history (strictly backend responsibility).
  • Relational data storage or direct interaction with PostgreSQL.
  • Ingesting raw hardware GPS data from Damri.

βš™οΈ Architecture & Design

System Diagram​

Architecture Decisions (ADRs)​

  • Language/Framework: React 19 + TypeScript built with Vite
  • Routing: TanStack React Router
  • State Management: Zustand
  • Styling & UI: Tailwind CSS v4
  • Map Visualization: Leaflet

πŸ’»Technical Specifications

API Documentation​

Data Model/Schema​

Project Structure​

main-website-cms
┣ .git
┃ ┣ hooks
┃ ┣ info
┃ ┃ β”— exclude
┃ ┣ logs
┃ ┃ β”— HEAD
┃ ┣ objects
┃ ┃ ┣ info
┃ ┃ β”— pack
┃ ┣ refs
┃ ┃ ┣ heads
┃ ┃ ┃ β”— main
┃ ┃ ┣ remotes
┃ ┃ ┃ β”— origin
┃ ┃ ┃ ┃ β”— HEAD
┃ ┃ β”— tags
┃ ┣ config
┃ ┣ description
┃ ┣ HEAD
┃ ┣ index
┃ β”— packed-refs
┣ .github
┃ β”— workflows
┃ ┃ ┣ deploy.yaml
┃ ┃ β”— preview.yaml
┣ .husky
┃ β”— pre-commit
┣ public
┃ ┣ decorators
┃ ┣ logo
┣ src
┃ ┣ app
┃ ┃ ┣ (routes)
┃ ┃ ┃ ┣ (auth)
┃ ┃ ┃ ┃ ┣ signin
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ ┣ auth-form.tsx
┃ ┃ ┃ ┃ ┃ ┃ β”— decorator.tsx
┃ ┃ ┃ ┃ ┃ ┣ layout.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ β”— signout
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ β”— (base)
┃ ┃ ┃ ┃ ┣ achievement
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ ┣ achievement-category
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ columns.tsx
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— table.tsx
┃ ┃ ┃ ┃ ┃ ┃ β”— achivement
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ columns.tsx
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— table.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ client
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ β”— columns.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ event
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ ┣ event
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ columns.tsx
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— table.tsx
┃ ┃ ┃ ┃ ┃ ┃ β”— event-category
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ columns.tsx
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— table.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ member
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ β”— columns.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ partnership
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ β”— columns.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ product
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ β”— columns.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ program
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ β”— columns.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ project
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ ┣ project
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ columns.tsx
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— table.tsx
┃ ┃ ┃ ┃ ┃ ┃ β”— tags
┃ ┃ ┃ ┃ ┃ ┃ ┃ ┣ columns.tsx
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— table.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ sig
┃ ┃ ┃ ┃ ┃ ┣ components
┃ ┃ ┃ ┃ ┃ ┃ β”— columns.tsx
┃ ┃ ┃ ┃ ┃ ┣ edit
┃ ┃ ┃ ┃ ┃ ┃ β”— [id]
┃ ┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ ┣ new
┃ ┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┃ ┃ ┣ layout.tsx
┃ ┃ ┃ ┃ ┣ loading.tsx
┃ ┃ ┃ ┃ β”— page.tsx
┃ ┃ ┣ api
┃ ┃ ┃ ┣ auth
┃ ┃ ┃ ┃ β”— [...nextauth]
┃ ┃ ┃ ┃ ┃ β”— route.ts
┃ ┃ ┃ β”— s3
┃ ┃ ┃ ┃ β”— upload-image
┃ ┃ ┃ ┃ ┃ β”— route.ts
┃ ┃ ┣ favicon.ico
┃ ┃ ┣ globals.css
┃ ┃ ┣ layout.tsx
┃ ┃ ┣ not-found.tsx
┃ ┃ β”— robots.txt
┃ ┣ components
┃ ┃ ┣ atoms
┃ ┃ ┃ ┣ Button
┃ ┃ ┃ ┃ β”— logout-button.tsx
┃ ┃ ┃ ┣ Form
┃ ┃ ┃ ┃ ┣ DateSelect.tsx
┃ ┃ ┃ ┃ ┣ ImageUpload.tsx
┃ ┃ ┃ ┃ ┣ MultipleSelect.tsx
┃ ┃ ┃ ┃ ┣ Select.tsx
┃ ┃ ┃ ┃ ┣ Switch.tsx
┃ ┃ ┃ ┃ β”— TextField.tsx
┃ ┃ ┃ ┣ ImageGallery
┃ ┃ ┃ ┃ β”— index.tsx
┃ ┃ ┃ ┣ Loading
┃ ┃ ┃ ┃ ┣ LoadingSkeleton.tsx
┃ ┃ ┃ ┃ β”— LoadingSuspense.tsx
┃ ┃ ┃ ┣ RetainAspectImage
┃ ┃ ┃ ┃ β”— index.tsx
┃ ┃ ┃ β”— index.ts
┃ ┃ ┣ organisms
┃ ┃ ┃ ┣ Form
┃ ┃ ┃ ┃ ┣ Achievement.tsx
┃ ┃ ┃ ┃ ┣ AchievementCategory.tsx
┃ ┃ ┃ ┃ ┣ Event.tsx
┃ ┃ ┃ ┃ ┣ EventCategory.tsx
┃ ┃ ┃ ┃ ┣ Member.tsx
┃ ┃ ┃ ┃ ┣ Product.tsx
┃ ┃ ┃ ┃ ┣ Program.tsx
┃ ┃ ┃ ┃ ┣ Project.tsx
┃ ┃ ┃ ┃ ┣ SIG.tsx
┃ ┃ ┃ ┃ ┣ Tag.tsx
┃ ┃ ┃ ┃ β”— types.ts
┃ ┃ ┃ ┣ RowDetails
┃ ┃ ┃ ┃ ┣ client
┃ ┃ ┃ ┃ ┃ β”— index.tsx
┃ ┃ ┃ ┃ β”— partnership
┃ ┃ ┃ ┃ ┃ β”— index.tsx
┃ ┃ ┃ ┣ TableDataAction
┃ ┃ ┃ ┃ β”— index.tsx
┃ ┃ ┃ β”— index.ts
┃ ┃ ┣ templates
┃ ┃ ┃ β”— index.ts
┃ ┃ ┣ ui
┃ ┣ config
┃ ┃ β”— index.ts
┃ ┣ constant
┃ ┃ ┣ general.ts
┃ ┃ ┣ image.ts
┃ ┃ ┣ navlinks.ts
┃ ┃ β”— tables.ts
┃ ┣ features
┃ ┃ ┣ cms
┃ ┃ ┃ ┣ achievement
┃ ┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ ┃ β”— type.ts
┃ ┃ ┃ ┣ client
┃ ┃ ┃ ┃ β”— api.ts
┃ ┃ ┃ ┣ event
┃ ┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ ┃ β”— type.ts
┃ ┃ ┃ ┣ member
┃ ┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ ┃ β”— type.ts
┃ ┃ ┃ ┣ partnership
┃ ┃ ┃ ┃ β”— api.ts
┃ ┃ ┃ ┣ product
┃ ┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ ┃ β”— type.ts
┃ ┃ ┃ ┣ program
┃ ┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ ┃ β”— type.ts
┃ ┃ ┃ ┣ project
┃ ┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ ┃ β”— type.ts
┃ ┃ ┃ ┣ sig
┃ ┃ ┃ ┃ β”— api.ts
┃ ┃ ┃ ┣ tag
┃ ┃ ┃ ┃ β”— api.ts
┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ β”— type.ts
┃ ┃ ┣ s3
┃ ┃ ┃ ┣ api.ts
┃ ┃ ┃ β”— type.ts
┃ ┃ β”— types.ts
┃ ┣ hooks
┃ ┃ ┣ useCounter.ts
┃ ┃ ┣ useCreateTableData.ts
┃ ┃ ┣ useDeleteTableData.ts
┃ ┃ β”— useUpdateTableData.ts
┃ ┣ lib
┃ ┣ providers
┃ ┃ ┣ index.ts
┃ ┃ ┣ redux-provider.tsx
┃ ┃ β”— session-provider.tsx
┃ ┣ schema
┃ ┃ ┣ achivement
┃ ┃ ┣ auth
┃ ┃ ┣ client
┃ ┃ ┣ event
┃ ┃ ┣ member
┃ ┃ ┣ partnership
┃ ┃ ┣ product
┃ ┃ ┣ program
┃ ┃ ┣ project
┃ ┃ ┣ sig
┃ ┃ β”— index.ts
┃ β”— types
┣ .env.example
┣ .eslintignore
┣ .eslintrc.json
┣ .gitignore
┣ .prettierignore
┣ .prettierrc.json
┣ components.json
┣ favicon.ico
┣ next.config.mjs
┣ package-lock.json
┣ package.json
┣ postcss.config.js
┣ README.md
┣ tailwind.config.ts
β”— tsconfig.json

Key Components​

main.go β€” Application Bootstrap

  • Reads config from .env via Viper
  • Creates pgxpool connection to PostgreSQL
  • Wires all handlers, services, repositories, and middlewares
  • Registers HTTP routes via utils.HandleRoute
  • Starts WebSocket broadcaster at /ws
  • Exposes webhook endpoint at /wh for GPS ingestion

app/bus/container.go β€” Runtime State Manager

  • Holds in-memory maps: busCoordinates, previousHalte, activeLaps, storedBuses, currentPlates

ApplyExternalCoordinates() β€” the main pipeline called by webhook: updates colours, detects lanes, tracks halte visits, detects lap start/end

  • DQ_SIZE = 50: rolling deque per bus for lane detection sampling

InitRuntimeState() β€” hydrates runtime caches from DB on startup

app/bus/halte.go β€” Geofence Proximity

  • 22 named haltes (bus stops) with fixed coordinates
  • nearestHalte(lat, lng) returns the closest halte name + distance in metres

Threshold: 45 metres β€” bus must be within 45m to register a halte visit

app/bus/route.go β€” Route Definitions

  • 4 route arrays: blueNormal, blueMorning, redNormal, redMorning
  • Pre-built halte-pair sets for O(1) lookup
  • detectRouteColorFromPair() returns blue / red / grey

app/auth/ β€” Authentication

  • SSO Login: validates CAS ticket with sso.ui.ac.id, auto-creates user in DB on first login
  • JWT pair (access + refresh) signed with HS256; expiry is configurable via env vars
  • Admin protection: API_KEY header is checked against ADMIN_API_KEY env var

☁️ Operational Playbook

Infrastructure​

  • Cloud Provider: AWS ECR + Pusilkom instance
  • Container Registry: 638207107223.dkr.ecr.ap-southeast-1.amazonaws.com
  • Image Name: bikun-tracker-v2-backend
  • Production Tag: stable (deployed from main branch)
  • Staging Tag: latest (deployed from staging branch)
  • Region: ap-southeast-1 (Singapore)

Environment Variables​

KeyDescriptionDefault (Dev)Sensitive?
DAMRI_APIDamri GPS API base URLhttps://api-damri.istsolutions.co.idNo
DAMRI_LOGIN_USERNAMEDamri API usernameβ€”Yes
DAMRI_LOGIN_PASSWORDDamri API passwordβ€”Yes
WS_URLExternal GPS WebSocket URLws://localhost:8000/statusNo
RM_APIRM lane-detection service URLhttps://eta-bikun-tracker-production.up.railway.appNo
PRINT_CSV_LOGSEnable CSV log POST to port 4040FALSENo
PORTPort the server listens on8080No
DB_HOSTPostgreSQL host addresslocalhostNo
DB_NAMEDatabase namebikun_trackerNo
DB_USERDatabase usernamepostgresNo
DB_PASSWORDDatabase passwordβ€”Yes
DB_PORTDatabase port5043No
WS_UPGRADE_WHITELISTAllowed WebSocket origins (comma-separated)localhost:5173No
JWT_EXPIRY_IN_DAYSAccess token validity in days1No
JWT_REFRESH_EXPIRY_IN_DAYSRefresh token validity in days30No
JWT_SECRET_KEYHMAC secret for JWT signingβ€”Yes
ADMIN_API_KEYAPI key for admin-protected endpointsβ€”Yes

πŸ™‹Questions

πŸ—’ List of frequently asked questions or question that need to be answered that is related to this initiative ..