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 vector tile styles (carto, openfreemap, openfreemap3d, positron, liberty, fiord)
  • 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!) {
listGpsData(page: $page, limit: $limit) {
data {
id
latitude
longitude
altitude
timestamp
speedKmh
}
total
page
limit
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.listGpsData.data.map((gps) => (
<tr key={gps.id}>
<td>{gps.latitude}</td>
<td>{gps.longitude}</td>
<td>{gps.speedKmh}</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​