Skip to main content

Backend

The Backend is the administrative interface for the Heimdall platform, built with Next.js 16 and the App Router.

Features

Dashboard

The main dashboard provides an overview of platform statistics and recent activity:

  • Active connections count
  • GPS data points
  • User statistics
  • Recent activity feed
  • Real-time updates via WebSocket

GPS Management

View and manage GPS tracking data:

  • Historical GPS data table with filtering, pagination, and bulk actions
  • Live map with real-time multi-device tracking via WebSocket
  • Device management (create, edit, assign API keys)
  • GPS settings (speed unit, coordinate format, timezone, retention)

Live Map Components

The /gps/map page uses several specialized map components from @elcto/ui/map:

  • LiveMarker -- Animated marker with heading arrow and speed badge, updates in real-time
  • MapDevicePanel -- Side panel for selecting which devices to display on the map
  • MapControls -- Zoom in/out and reset view buttons
  • MapTileSelector -- Switch between 6 tile providers (e.g. OpenStreetMap, satellite imagery)
  • MapRoute -- Renders waypoint polylines for trip routes
  • MapPolygon -- Displays geofence boundaries on the map

User Management

Manage platform users (Admin only):

  • View all registered users
  • View and edit user profiles
  • Assign roles and permissions
  • View user activity and login history
  • Suspend/ban users
  • Force logout sessions

Role Management

Create and manage roles with fine-grained permissions:

  • Create custom roles
  • Assign permissions to roles
  • Set role-based 2FA requirements
  • View role assignments

Trip Management

Plan and track trips with full lifecycle support:

  • Trip overview with grid/list toggle, status filters, and search
  • Create trips from scratch or from reusable templates
  • Trip detail view with live map tracking and status timeline
  • Edit trip metadata (category, locations, weight, tags, notes)
  • Manage trip categories with hierarchical nesting and color coding
  • Location directory with address, coordinates, and contact details
  • Trip templates for quick creation with pre-filled defaults

API Key Management

Manage API keys for system access:

  • Create new API keys with permission scoping
  • View and edit existing keys
  • View key usage statistics
  • Revoke API keys

Developer Tools

Tools for developers integrating with Heimdall:

  • OAuth application management
  • Create/edit OAuth apps
  • View client credentials
  • Manage redirect URIs
  • API key creation and detail views

Pages & Routes

RouteDescriptionAuth Required
/Landing pageNo
/loginLogin pageNo
/errorAuth error pageNo
/signoutSign-out with token revocationNo
/unauthorizedUnauthorized access pageNo
/dashboardMain dashboard with stats and activity feedYes
Users
/usersUser list with search and role filtersYes (Admin)
/users/bannedBanned users list with unban actionsYes (Admin)
/users/[id]User detail with profile, roles, sessions, and bansYes (Admin)
GPS
/gpsGPS history table with filtering, pagination, and bulk deleteYes
/gps/mapLive map with multi-device tracking via WebSocketYes
/gps/devicesDevice list with grid/list toggle and status indicatorsYes
/gps/devices/createRegister a new GPS device with API key assignmentYes
/gps/devices/[id]/editEdit device name, type, status, and API key bindingYes
/gps/settingsGPS display settings (speed unit, coordinate format, timezone, retention)Yes
Trips
/tripsTrip overview with grid/list toggle, status and category filtersYes
/trips/createCreate a new trip (or pre-fill from a template)Yes
/trips/[id]Trip detail view with live map, status timeline, and GPS dataYes
/trips/[id]/editEdit trip metadata, status, category, and locationsYes
/trips/categoriesManage hierarchical trip categories with colorsYes
/trips/locationsLocation directory with grid/list view and searchYes
/trips/locations/createCreate a location with address, coordinates, and contactsYes
/trips/locations/[id]/editEdit location details and contactsYes
/trips/templatesTrip template list with grid/list viewYes
/trips/templates/createCreate a reusable trip template with defaultsYes
/trips/templates/[id]/editEdit trip template settingsYes
Developer
/developer/api-keysAPI key list with usage statsYes
/developer/api-keys/newCreate a new API key with permission scopingYes
/developer/api-keys/[id]View and edit API key details, regenerate secretYes
/developer/oauth-appsOAuth application listYes
/developer/oauth-apps/newRegister a new OAuth applicationYes
/developer/oauth-apps/[id]Edit OAuth app (credentials, redirect URIs, scopes)Yes
System
/system/auditAudit log with filters, search, and paginationYes (Admin)
/system/audit/[auditId]Audit event detail with metadata and geo infoYes (Admin)
/system/audit/reportsAudit summary reportsYes (Admin)
/system/integrationsBot and service integration statusYes (Admin)
/system/permissionsPermission list and managementYes (Admin)
/system/permissions/createCreate a new permission entryYes (Admin)
/system/permissions/[permissionId]Edit permission detailsYes (Admin)
/system/rolesRole listYes (Admin)
/system/roles/createCreate a new role with permissionsYes (Admin)
/system/roles/[roleId]Edit role and assigned permissionsYes (Admin)
/system/platform/settingsPlatform-wide settings overviewYes (Admin)
/system/platform/settings/[slug]Edit a specific platform setting groupYes (Admin)
/system/settingsSystem feature togglesYes (Admin)
Bots
/bots/discord/guildsDiscord guild overview with member and channel countsYes (Admin)
/bots/discord/guilds/[guildId]Guild detail dashboardYes (Admin)
/bots/discord/guilds/[guildId]/channelsGuild channel listYes (Admin)
/bots/discord/guilds/[guildId]/membersGuild member listYes (Admin)
/bots/discord/guilds/[guildId]/rolesGuild role listYes (Admin)
/bots/discord/guilds/[guildId]/settingsGuild-specific bot settingsYes (Admin)
/bots/discord/settingsGlobal Discord bot settingsYes (Admin)

Authentication

The Backend Dashboard uses NextAuth with a custom Heimdall provider:

// src/lib/auth/heimdall-provider.ts
import { OAuthConfig } from "next-auth/providers";

export const HeimdallProvider: OAuthConfig<Profile> = {
id: "heimdall",
name: "Heimdall",
type: "oauth",
authorization: {
url: `${process.env.HEIMDALL_ID_URL}/oauth/authorize`,
params: { scope: "openid profile email" }
},
token: `${process.env.API_URL}/v1/oauth/token`,
userinfo: `${process.env.API_URL}/v1/oauth/userinfo`,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
};
}
};

Session Handling

Sessions are managed via NextAuth with JWT strategy. Next.js 16 renamed middleware.ts to proxy.ts:

// proxy.ts (Next.js 16+)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";

export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;

// Get the token (session) - use app-specific cookie name
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
cookieName: process.env.NODE_ENV === "production"
? "__Secure-backend.session-token"
: "backend.session-token",
});

// Require authentication for protected routes
if (!token && pathname.startsWith("/dashboard")) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}

return NextResponse.next();
}

export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

Components

Protected Route

Wrap pages that require authentication:

import { ProtectedRoute } from "@/components/auth/ProtectedRoute";

export default function DashboardPage() {
return (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
);
}

Protected Content

Conditionally render content based on authentication:

import { ProtectedContent } from "@/components/auth/ProtectedContent";

export default function Page() {
return (
<ProtectedContent
fallback={<LoginPrompt />}
requiredPermissions={["gps:read"]}
>
<GpsDataTable />
</ProtectedContent>
);
}

User Button

Display user info and logout:

import { UserButton } from "@/components/auth/UserButton";

export default function Header() {
return (
<nav>
<UserButton />
</nav>
);
}

Hooks

useAuth

Access authentication state:

import { useAuth } from "@/hooks/useAuth";

function Component() {
const { user, isLoading, isAuthenticated } = useAuth();

if (isLoading) return <Spinner />;
if (!isAuthenticated) return <LoginPrompt />;

return <div>Welcome, {user.name}</div>;
}

usePermissions

Check user permissions:

import { usePermissions } from "@/hooks/usePermissions";

function Component() {
const { permissions, hasPermission, isLoading } = usePermissions();

if (hasPermission("gps:write")) {
return <CreateGpsButton />;
}

return null;
}

API Integration

GraphQL Client

The dashboard uses Apollo Client for GraphQL:

// src/components/providers/ApolloWrapper.tsx
"use client";

import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
import { useSession } from "next-auth/react";

export function ApolloWrapper({ children }) {
const { data: session } = useSession();

const client = new ApolloClient({
uri: `${process.env.NEXT_PUBLIC_API_URL}/v1/gql`,
cache: new InMemoryCache(),
headers: {
Authorization: session?.accessToken
? `Bearer ${session.accessToken}`
: ""
}
});

return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

Example Query

import { gql, useQuery } from "@apollo/client";

const GET_GPS_DATA = gql`
query GetGpsData($page: Int!, $limit: Int!) {
gpsList(page: $page, limit: $limit) {
data {
id
latitude
longitude
altitude
timestamp
speed
}
pagination {
page
limit
total
totalPages
}
}
}
`;

function GpsDataTable() {
const { data, loading, error } = useQuery(GET_GPS_DATA, {
variables: { page: 1, limit: 10 }
});

if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;

return (
<table>
{data.gpsList.data.map((gps) => (
<tr key={gps.id}>
<td>{gps.latitude}</td>
<td>{gps.longitude}</td>
<td>{gps.speed}</td>
</tr>
))}
</table>
);
}

Layouts

Dashboard Layout

The main layout for authenticated pages:

// src/app/dashboard/layout.tsx
import { DashboardLayout } from "@/components/layouts/DashboardLayout";

export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>;
}

Features:

  • Sidebar navigation
  • Header with user menu
  • Breadcrumbs
  • Footer

Overlay Layout

Full-screen overlay layout for modals:

import { OverlayLayout } from "@/layouts/OverlayLayout";

export default function ModalPage() {
return (
<OverlayLayout>
<Modal />
</OverlayLayout>
);
}

UI Components

The dashboard includes a set of reusable UI components:

Button

import { Button } from "@/components/ui/Button";

<Button variant="primary" size="lg" onClick={handleClick}>
Click Me
</Button>

<Button variant="outline" disabled>
Disabled
</Button>

Alert

import { Alert } from "@/components/ui/Alert";

<Alert type="success" title="Success!">
Your changes have been saved.
</Alert>

<Alert type="error" title="Error">
Something went wrong.
</Alert>
import { Modal } from "@/components/ui/Modal";

<Modal isOpen={isOpen} onClose={handleClose} title="Confirm Action">
<p>Are you sure you want to proceed?</p>
<div>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={handleConfirm}>
Confirm
</Button>
</div>
</Modal>

Loading Spinner

import { LoadingSpinner } from "@/components/ui/LoadingSpinner";

<LoadingSpinner size="lg" />

Floating Input

import { FloatingInput } from "@/components/ui/FloatingInput";

<FloatingInput
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>

Environment Variables

VariableDescriptionRequired
NEXTAUTH_URLThe base URL of the appYes
NEXTAUTH_SECRETSecret for signing tokensYes
NEXT_PUBLIC_API_URLPublic API URLYes
API_URLServer-side API URLYes
HEIMDALL_ID_URLHeimdall ID service URLYes

Development

cd platform/backend

# Install dependencies
pnpm install

# Run development server
pnpm dev

# Build for production
pnpm build

# Start production server
pnpm start

# Run linting
pnpm lint

# Run E2E tests
pnpm test:e2e

Next Steps