Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
.next
node_modules
npm-debug.log
data
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NOTIFICATION_SERVICE_URL=http://localhost:4001
NEXT_PUBLIC_WS_URL=ws://localhost:4001/ws
HUB_HOST=0.0.0.0
HUB_PORT=4001
SQLITE_PATH=./data/notifications.sqlite
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
.next
out
dist
coverage
*.tsbuildinfo
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
data
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM oven/bun:1.2.15-alpine AS builder

WORKDIR /app

COPY package.json ./
RUN bun install
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun --bun next build --webpack

FROM oven/bun:1.2.15-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/next.config.ts ./next.config.ts

EXPOSE 3000

CMD ["bun", "--bun", "next", "start", "--hostname", "0.0.0.0", "--port", "3000"]
222 changes: 149 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,121 +1,197 @@
## Technical Challenge — Senior
# Logistics Notification Hub

### Background
Full-stack implementation of the senior logistics challenge. The project provides:

A growing logistics company needs an internal tool to monitor delivery events across multiple warehouses in real time. Operations managers want to see live updates as packages move through different stages, receive alerts when something goes wrong, and review historical data.
- A public Next.js application with App Router
- A separate Bun notification hub with native WebSocket support
- SQLite persistence for notification history
- A real-time dashboard with channel filtering and live updates
- Docker Compose orchestration for the full stack

In this challenge, you'll build a simplified version of this system: a real-time notification hub where events are created via API, stored in a database, broadcast to connected clients via WebSocket, and displayed in a simple dashboard.
## Stack

We are **not evaluating specific tools or patterns**. We simply want to understand how you think, how you code, and how you approach real-world problems. Be yourself.
### Backend

- Runtime: Bun
- API framework: Next.js App Router
- WebSocket server: Bun native WebSocket
- Database: SQLite

### What You Need to Build
### Frontend

A functional **full stack application** with:
- Next.js
- Tailwind CSS
- shadcn/ui

1. A WebSocket server that broadcasts notifications to subscribed clients
2. A REST API to create and query notifications
3. A database to persist notification history
4. A UI that displays notifications in real time
5. Docker Compose to run the entire system
### Infrastructure

- Docker
- Docker Compose

### Database Schema
## Features

Design the schema yourself. At minimum, you should be able to store:
- `POST /api/notifications` to create notifications
- `GET /api/notifications` with filtering by channel, priority, date range, limit, and offset
- `GET /api/channels` with channel totals
- WebSocket subscriptions by channel
- Broadcast on notification creation
- Hub healthcheck at `GET /health`
- Real-time dashboard with connection status, filters, and live feed

- Notifications with: channel, title, message, priority, and timestamp
- Channels as a concept (either as a separate table or embedded — your call)
## Project Structure

Include appropriate indexes for the queries your API supports.
- `src/app/api/*`: public API routes in Next.js
- `src/components/notification-dashboard.tsx`: dashboard container and orchestration
- `src/components/dashboard/*`: modular dashboard panels and presentation helpers
- `src/lib/contracts.ts`: shared schemas and types
- `services/notification-hub/index.ts`: Bun hub, SQLite access, and WebSocket publishing
- `docker-compose.yml`: app + hub orchestration

## Run Locally

### Tech Stack
### Option 1: Docker Compose

#### Backend
```bash
docker compose up --build
```

* Runtime: **Bun**
* API Framework: **Next.js** (App Router)
* WebSocket Server: **Bun native WebSocket** (separate service)
* Database: **SQLite** (ORM, query builder, or raw SQL — your choice)
Services:

#### Frontend
- App and public API: [http://localhost:3000](http://localhost:3000)
- Hub and healthcheck: [http://localhost:4001](http://localhost:4001)
- WebSocket endpoint: `ws://localhost:4001/ws`

* Framework: **Next.js**
* Styling: **TailwindCSS**
* Additional UI libraries are welcome but not required
### Option 2: Local development

#### Infrastructure
Requirements:

* **Docker** + **Docker Compose**
- Bun

Install dependencies:

### Required API Endpoints
```bash
bun install
```

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/notifications` | Create a notification (channel, title, message, priority) |
| GET | `/api/notifications` | List notifications with filters (channel, priority, date range, limit/offset) |
| GET | `/api/channels` | List channels with notification count |
Run the Next.js app:

- Validate input. Return proper HTTP status codes.
- Use a consistent response structure.
```bash
bun run dev
```

Run the Bun notification hub in a separate terminal:

### WebSocket Server
```bash
bun run hub
```

- Clients connect and subscribe to one or more channels.
- When a notification is created via the API, broadcast it to all clients subscribed to that channel.
- Expose a `GET /health` endpoint.
Optional environment variables:

```bash
NOTIFICATION_SERVICE_URL=http://localhost:4001
NEXT_PUBLIC_WS_URL=ws://localhost:4001/ws
HUB_HOST=0.0.0.0
HUB_PORT=4001
SQLITE_PATH=./data/notifications.sqlite
```

### Required UI
## API Examples

A single page that:
Create a notification:

1. Connects to the WebSocket server
2. Shows a connection status indicator (connected/disconnected)
3. Displays notifications in real time as they arrive
4. Allows filtering by channel
```bash
curl -X POST http://localhost:3000/api/notifications \
-H "Content-Type: application/json" \
-d "{\"channel\":\"warehouse-east\",\"title\":\"Delay detected\",\"message\":\"Truck 28 is 18 minutes behind schedule.\",\"priority\":\"high\"}"
```

List notifications:

### Docker
```bash
curl "http://localhost:3000/api/notifications?channel=warehouse-east&priority=high&limit=20&offset=0"
```

- `docker-compose.yml` that starts the WebSocket server, the Next.js app, and any other service needed.
- Must work with a single `docker compose up`.
List channels:

```bash
curl "http://localhost:3000/api/channels"
```

### Submission Instructions
## Architecture and Decisions

* **Fork this repository**, complete your work, and **submit a pull request**.
* Include a `README.md` with:
* Clear instructions to run the project locally
* A short explanation of your architecture and design decisions
* Any trade-offs or things you would improve with more time
### 1. Next.js as the public edge

The challenge requires Next.js App Router for the API, so the public routes live in `src/app/api/*`. These routes:

### Time Expectation
- validate request bodies and query params with Zod
- return a consistent `success/data/meta/error` shape
- proxy requests to the internal Bun service

You should spend no more than **3 hours** on this task.
### 2. Bun notification hub as the operational core

Don't worry if you can't finish everything. What matters most is **how far you get** and **how you approach the problem**.
The Bun service owns:

- SQLite persistence
- notification querying
- WebSocket subscription management
- publishing newly created notifications
- health checks

### Evaluation & Guidance
This keeps database writes and WebSocket fanout in one place, which simplifies the flow for a challenge-sized project.

What we mainly evaluate:
### 3. SQLite schema and indexes

- Solution design and structure (architecture, modularity, separation of concerns).
- Clarity of reasoning and documentation (decisions, trade-offs, assumptions).
- Code quality (readability, consistency, error handling, good practices).
- WebSocket implementation (scalability, reconnection handling, channel management).
- Docker setup (networking, multi-service orchestration).
- Git workflow (incremental commits with clear messages).
- Prioritization and scope management: it's valid to leave items pending if you explain what and why.
The `notifications` table stores:

Use of AI (optional but allowed):
- `channel`
- `title`
- `message`
- `priority`
- `created_at`

- You may use AI tools (e.g., Claude, Copilot, ChatGPT, Cursor) to assist with your solution.
- We care most about how you structure the solution and explain your decisions.
- If you used AI, add a brief note in your PR/README: which tools you used, which parts were assisted, and what changes you made after review.
- Only include code you understand and can justify.
Indexes included:

- `(channel, created_at DESC)`
- `(priority, created_at DESC)`
- `(channel, priority, created_at DESC)`
- `(created_at DESC)`

These align with the filtering patterns supported by the API.

### 4. Frontend design

The dashboard was rebuilt with modular panels and shadcn/ui primitives. The visual direction is inspired by HextaUI blocks such as:

- `team-dashboard`
- `task-filters`
- `task-progress`
- `task-board`

The current structure separates orchestration, panel layout, and presentational helpers to keep the codebase reviewable.

## Quality Improvements

- Security headers in Next.js: CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, and `Permissions-Policy`
- `robots` set to `noindex` for an internal tool
- Accessible labels, `aria-live` regions, skip link, and keyboard-safe controls
- Reduced unnecessary client work by merging created notifications locally instead of forcing a full refresh
- Reconnection handling with exponential backoff for the WebSocket client

## Trade-offs

- I kept SQLite inside the Bun hub instead of splitting data access across services. This reduces complexity and avoids duplicated write logic.
- The public Next.js API is intentionally thin. In a larger system I would consider auth, rate limiting, and shared observability around the edge layer.
- For a 3-hour challenge I prioritized a simple internal HTTP bridge between Next.js and Bun rather than adding Redis or an event bus.
- Automated end-to-end tests are not included. With more time, they would be the next priority after delivery.

## What I Would Improve With More Time

- Add authentication and role-based access for internal users
- Add automated integration and E2E tests
- Add observability around WebSocket connections and broadcast failures
- Add a queue or pub/sub layer for horizontal scaling
- Expose richer analytics per warehouse/channel

## AI Usage

Codex/ChatGPT was used to assist with scaffolding, implementation, refactoring, documentation, and technical review. The final solution was reviewed and adjusted during development, and only code that was understood and validated was kept.
25 changes: 25 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
44 changes: 44 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
services:
notification-hub:
build:
context: .
dockerfile: services/notification-hub/Dockerfile
container_name: notification-hub
environment:
HUB_HOST: 0.0.0.0
HUB_PORT: 4001
SQLITE_PATH: /data/notifications.sqlite
ports:
- "4001:4001"
volumes:
- notification-data:/data
healthcheck:
test:
[
"CMD",
"bun",
"-e",
"fetch('http://127.0.0.1:4001/health').then((res) => process.exit(res.ok ? 0 : 1)).catch(() => process.exit(1))",
]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s

app:
build:
context: .
dockerfile: Dockerfile
container_name: logistics-dashboard
depends_on:
notification-hub:
condition: service_healthy
environment:
NODE_ENV: production
NOTIFICATION_SERVICE_URL: http://notification-hub:4001
NEXT_PUBLIC_WS_URL: ws://localhost:4001/ws
ports:
- "3000:3000"

volumes:
notification-data:
Loading