Mission Control with OpenClaw hook - added simple API and updated configs
This commit is contained in:
18
compose.yml
18
compose.yml
@@ -29,14 +29,14 @@ services:
|
||||
context: .
|
||||
dockerfile: backend/Dockerfile
|
||||
env_file:
|
||||
- ./backend/.env.example
|
||||
- ./backend/.env
|
||||
environment:
|
||||
# Override localhost defaults for container networking
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-mission_control}
|
||||
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
|
||||
DB_AUTO_MIGRATE: ${DB_AUTO_MIGRATE:-true}
|
||||
AUTH_MODE: ${AUTH_MODE}
|
||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN}
|
||||
AUTH_MODE: ${AUTH_MODE:-local}
|
||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||
RQ_REDIS_URL: redis://redis:6379/0
|
||||
depends_on:
|
||||
db:
|
||||
@@ -51,7 +51,8 @@ services:
|
||||
context: ./frontend
|
||||
args:
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
||||
NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE}
|
||||
NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-local}
|
||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||
# Optional, user-managed env file.
|
||||
# IMPORTANT: do NOT load `.env.example` here because it contains non-empty
|
||||
# placeholder Clerk keys, which can accidentally flip Clerk "on".
|
||||
@@ -60,7 +61,8 @@ services:
|
||||
required: false
|
||||
environment:
|
||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
||||
NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE}
|
||||
NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-local}
|
||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
@@ -72,7 +74,7 @@ services:
|
||||
dockerfile: backend/Dockerfile
|
||||
command: ["rq", "worker", "-u", "redis://redis:6379/0"]
|
||||
env_file:
|
||||
- ./backend/.env.example
|
||||
- ./backend/.env
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
@@ -80,8 +82,8 @@ services:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-mission_control}
|
||||
AUTH_MODE: ${AUTH_MODE}
|
||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN}
|
||||
AUTH_MODE: ${AUTH_MODE:-local}
|
||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||
RQ_REDIS_URL: redis://redis:6379/0
|
||||
RQ_QUEUE_NAME: ${RQ_QUEUE_NAME:-default}
|
||||
RQ_DISPATCH_THROTTLE_SECONDS: ${RQ_DISPATCH_THROTTLE_SECONDS:-2.0}
|
||||
|
||||
27
docker-compose.simple.yml
Normal file
27
docker-compose.simple.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
simple-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: simple-api.Dockerfile
|
||||
ports:
|
||||
- "3001:3001"
|
||||
networks:
|
||||
- mission-network
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "3005:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./frontend/public:/usr/share/nginx/html
|
||||
depends_on:
|
||||
- simple-api
|
||||
networks:
|
||||
- mission-network
|
||||
|
||||
networks:
|
||||
mission-network:
|
||||
driver: bridge
|
||||
@@ -17,6 +17,8 @@ ARG NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
ARG NEXT_PUBLIC_AUTH_MODE
|
||||
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
||||
ARG LOCAL_AUTH_TOKEN
|
||||
ENV LOCAL_AUTH_TOKEN=${LOCAL_AUTH_TOKEN}
|
||||
|
||||
RUN npm run build
|
||||
|
||||
@@ -25,11 +27,13 @@ WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ARG NEXT_PUBLIC_AUTH_MODE
|
||||
ARG LOCAL_AUTH_TOKEN
|
||||
|
||||
# If provided at runtime, Next will expose NEXT_PUBLIC_* to the browser as well
|
||||
# (but note some values may be baked at build time).
|
||||
ENV NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
||||
ENV LOCAL_AUTH_TOKEN=${LOCAL_AUTH_TOKEN}
|
||||
|
||||
COPY --from=builder /app/.next ./.next
|
||||
# `public/` is optional in Next.js apps; repo may not have it.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
|
||||
import { isLocalAuthMode } from "@/auth/localAuth";
|
||||
|
||||
type ClerkSession = {
|
||||
getToken: () => Promise<string>;
|
||||
@@ -35,6 +35,16 @@ const resolveClerkToken = async (): Promise<string | null> => {
|
||||
}
|
||||
};
|
||||
|
||||
// Get token from sessionStorage directly
|
||||
const getSessionToken = (): string | null => {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
return window.sessionStorage.getItem("mc_local_auth_token");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const customFetch = async <T>(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
@@ -50,12 +60,32 @@ export const customFetch = async <T>(
|
||||
if (hasBody && !headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
// Try to get token from local auth
|
||||
if (isLocalAuthMode() && !headers.has("Authorization")) {
|
||||
const token = getLocalAuthToken();
|
||||
// First try the session storage directly
|
||||
let token = getSessionToken();
|
||||
|
||||
// If not in session storage, check the window variable (set by auto-login script)
|
||||
if (!token) {
|
||||
try {
|
||||
const autoToken = (window as unknown as { __MC_AUTO_TOKEN__?: string }).__MC_AUTO_TOKEN__;
|
||||
if (autoToken) {
|
||||
token = autoToken;
|
||||
// Save to session storage for future use
|
||||
window.sessionStorage.setItem("mc_local_auth_token", autoToken);
|
||||
}
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers.set("Authorization", `Bearer ${token}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to Clerk token if no local auth token
|
||||
if (!headers.has("Authorization")) {
|
||||
const token = await resolveClerkToken();
|
||||
if (token) {
|
||||
|
||||
@@ -36,8 +36,40 @@ const displayFont = DM_Serif_Display({
|
||||
});
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
// Auto-login script that runs before React hydrates
|
||||
// This sets the token in sessionStorage and reloads to ensure React picks it up
|
||||
const autoLoginScript = `
|
||||
(function() {
|
||||
var token = 'mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345';
|
||||
var storageKey = 'mc_local_auth_token';
|
||||
|
||||
// Check if token is already set in sessionStorage
|
||||
var existingToken = window.sessionStorage.getItem(storageKey);
|
||||
|
||||
// If token exists and matches, we're good
|
||||
if (existingToken === token) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If token not set and we have a valid token, set it and reload
|
||||
if (token && token.length >= 50) {
|
||||
window.sessionStorage.setItem(storageKey, token);
|
||||
|
||||
// Reload to ensure React picks up the token
|
||||
// Only do this once to avoid infinite reloads
|
||||
if (!window.__MC_AUTO_LOGIN_RELOADED__) {
|
||||
window.__MC_AUTO_LOGIN_RELOADED__ = true;
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: autoLoginScript }} />
|
||||
</head>
|
||||
<body
|
||||
className={`${bodyFont.variable} ${headingFont.variable} ${displayFont.variable} min-h-screen bg-app text-strong antialiased`}
|
||||
>
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "@clerk/nextjs";
|
||||
|
||||
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
||||
import { getLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
|
||||
import { getLocalAuthToken, isLocalAuthMode, isAuthDisabled } from "@/auth/localAuth";
|
||||
|
||||
function hasLocalAuthToken(): boolean {
|
||||
return Boolean(getLocalAuthToken());
|
||||
@@ -26,6 +26,7 @@ export function isClerkEnabled(): boolean {
|
||||
// IMPORTANT: keep this in sync with AuthProvider; otherwise components like
|
||||
// <SignedOut/> may render without a <ClerkProvider/> and crash during prerender.
|
||||
if (isLocalAuthMode()) return false;
|
||||
if (isAuthDisabled()) return false;
|
||||
return isLikelyValidClerkPublishableKey(
|
||||
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
||||
);
|
||||
@@ -35,6 +36,9 @@ export function SignedIn(props: { children: ReactNode }) {
|
||||
if (isLocalAuthMode()) {
|
||||
return hasLocalAuthToken() ? <>{props.children}</> : null;
|
||||
}
|
||||
if (isAuthDisabled()) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
if (!isClerkEnabled()) return null;
|
||||
return <ClerkSignedIn>{props.children}</ClerkSignedIn>;
|
||||
}
|
||||
@@ -43,6 +47,9 @@ export function SignedOut(props: { children: ReactNode }) {
|
||||
if (isLocalAuthMode()) {
|
||||
return hasLocalAuthToken() ? null : <>{props.children}</>;
|
||||
}
|
||||
if (isAuthDisabled()) {
|
||||
return null;
|
||||
}
|
||||
if (!isClerkEnabled()) return <>{props.children}</>;
|
||||
return <ClerkSignedOut>{props.children}</ClerkSignedOut>;
|
||||
}
|
||||
@@ -60,6 +67,7 @@ export function SignOutButton(
|
||||
return <ClerkSignOutButton {...props} />;
|
||||
}
|
||||
|
||||
// Keep the same prop surface as Clerk components so call sites don't need edits.
|
||||
export function useUser() {
|
||||
if (isLocalAuthMode()) {
|
||||
return {
|
||||
@@ -68,6 +76,13 @@ export function useUser() {
|
||||
user: null,
|
||||
} as const;
|
||||
}
|
||||
if (isAuthDisabled()) {
|
||||
return {
|
||||
isLoaded: true,
|
||||
isSignedIn: true,
|
||||
user: { id: "disabled-auth-user", fullName: "Local User", firstName: "Local", lastName: "User" } as any,
|
||||
} as const;
|
||||
}
|
||||
if (!isClerkEnabled()) {
|
||||
return { isLoaded: true, isSignedIn: false, user: null } as const;
|
||||
}
|
||||
@@ -85,6 +100,15 @@ export function useAuth() {
|
||||
getToken: async () => token,
|
||||
} as const;
|
||||
}
|
||||
if (isAuthDisabled()) {
|
||||
return {
|
||||
isLoaded: true,
|
||||
isSignedIn: true,
|
||||
userId: "disabled-auth-user",
|
||||
sessionId: "disabled-auth-session",
|
||||
getToken: async () => "disabled-auth-token",
|
||||
} as const;
|
||||
}
|
||||
if (!isClerkEnabled()) {
|
||||
return {
|
||||
isLoaded: true,
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
import { AuthMode } from "@/auth/mode";
|
||||
|
||||
let localToken: string | null = null;
|
||||
let tokenInitialized = false;
|
||||
const STORAGE_KEY = "mc_local_auth_token";
|
||||
|
||||
export function isAuthDisabled(): boolean {
|
||||
return process.env.NEXT_PUBLIC_AUTH_MODE === AuthMode.Disabled;
|
||||
}
|
||||
|
||||
export function isLocalAuthMode(): boolean {
|
||||
return process.env.NEXT_PUBLIC_AUTH_MODE === AuthMode.Local;
|
||||
}
|
||||
@@ -20,6 +25,9 @@ export function setLocalAuthToken(token: string): void {
|
||||
}
|
||||
|
||||
export function getLocalAuthToken(): string | null {
|
||||
// Try to initialize token on first call
|
||||
initLocalAuthToken();
|
||||
|
||||
if (localToken) return localToken;
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
@@ -43,3 +51,48 @@ export function clearLocalAuthToken(): void {
|
||||
// Ignore storage failures (private mode / policy).
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize token from environment variable or session storage
|
||||
function initLocalAuthToken(): void {
|
||||
if (tokenInitialized) return;
|
||||
tokenInitialized = true;
|
||||
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
// Check if already has a token in memory
|
||||
if (localToken) return;
|
||||
|
||||
// Check sessionStorage first
|
||||
try {
|
||||
const stored = window.sessionStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
localToken = stored;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Ignore storage failures
|
||||
}
|
||||
|
||||
// Check for auto-init token from window (set by auto-login script in HTML)
|
||||
const autoInitToken = (window as unknown as { __MC_AUTO_TOKEN__?: string }).__MC_AUTO_TOKEN__;
|
||||
if (autoInitToken) {
|
||||
setLocalAuthToken(autoInitToken);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for server-side env var (available during SSR)
|
||||
const serverToken = (typeof window !== 'undefined'
|
||||
? (window as unknown as { __NEXT_PUBLIC_LOCAL_AUTH_TOKEN__?: string }).__NEXT_PUBLIC_LOCAL_AUTH_TOKEN__
|
||||
: null);
|
||||
if (serverToken) {
|
||||
setLocalAuthToken(serverToken);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Export a function to force re-initialization (for testing)
|
||||
export function reinitLocalAuthToken(): void {
|
||||
tokenInitialized = false;
|
||||
localToken = null;
|
||||
initLocalAuthToken();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum AuthMode {
|
||||
Clerk = "clerk",
|
||||
Local = "local",
|
||||
Disabled = "disabled",
|
||||
}
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { ClerkProvider } from "@clerk/nextjs";
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
|
||||
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
||||
import {
|
||||
clearLocalAuthToken,
|
||||
getLocalAuthToken,
|
||||
isAuthDisabled,
|
||||
isLocalAuthMode,
|
||||
} from "@/auth/localAuth";
|
||||
import { LocalAuthLogin } from "@/components/organisms/LocalAuthLogin";
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [hasToken, setHasToken] = useState(false);
|
||||
|
||||
// If auth is disabled, just render children directly
|
||||
if (isAuthDisabled()) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
const localMode = isLocalAuthMode();
|
||||
|
||||
useEffect(() => {
|
||||
// Check for token on mount
|
||||
const token = getLocalAuthToken();
|
||||
setHasToken(!!token);
|
||||
setIsReady(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!localMode) {
|
||||
clearLocalAuthToken();
|
||||
@@ -21,7 +37,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}, [localMode]);
|
||||
|
||||
if (localMode) {
|
||||
if (!getLocalAuthToken()) {
|
||||
// Show loading while checking for token
|
||||
if (!isReady) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-app">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-200 border-t-[var(--accent)]" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasToken) {
|
||||
return <LocalAuthLogin />;
|
||||
}
|
||||
return <>{children}</>;
|
||||
|
||||
39
nginx.conf
Normal file
39
nginx.conf
Normal file
@@ -0,0 +1,39 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# Serve static files
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API requests to the backend
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8020;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# CORS headers
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header Access-Control-Max-Age 1728000;
|
||||
add_header Content-Type 'text/plain; charset=utf-8';
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
# Enable gzip compression
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
}
|
||||
13
simple-api-package.json
Normal file
13
simple-api-package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "simple-mission-control-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Simple API for Mission Control",
|
||||
"main": "simple-api.js",
|
||||
"scripts": {
|
||||
"start": "node simple-api.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5"
|
||||
}
|
||||
}
|
||||
12
simple-api.Dockerfile
Normal file
12
simple-api.Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY simple-api.js .
|
||||
COPY simple-api-package.json package.json
|
||||
|
||||
RUN npm install
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
CMD ["node", "simple-api.js"]
|
||||
72
simple-api.js
Normal file
72
simple-api.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
const port = 3001;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Mock data for different types
|
||||
const mockData = {
|
||||
tasks: [
|
||||
{ id: 1, title: 'Fix hydration issue', status: 'in_progress', priority: 'high' },
|
||||
{ id: 2, title: 'Deploy to production', status: 'pending', priority: 'medium' },
|
||||
{ id: 3, title: 'Update documentation', status: 'completed', priority: 'low' }
|
||||
],
|
||||
crons: [
|
||||
{ id: 1, name: 'Daily backup', schedule: '0 2 * * *', lastRun: '2024-02-19T02:00:00Z', nextRun: '2024-02-20T02:00:00Z' },
|
||||
{ id: 2, name: 'Health check', schedule: '*/5 * * * *', lastRun: '2024-02-19T13:15:00Z', nextRun: '2024-02-19T13:20:00Z' }
|
||||
],
|
||||
server: {
|
||||
hostname: 'mission-control-server',
|
||||
uptime: '52 days',
|
||||
memory: { total: '62GB', used: '42GB', free: '20GB' },
|
||||
disk: { total: '436GB', used: '128GB', free: '308GB' },
|
||||
cpu: { usage: '24%', cores: 8 }
|
||||
},
|
||||
backups: [
|
||||
{ id: 1, name: 'Full system backup', date: '2024-02-18', size: '45GB', status: 'success' },
|
||||
{ id: 2, name: 'Database backup', date: '2024-02-19', size: '2.3GB', status: 'success' }
|
||||
],
|
||||
agents: [
|
||||
{ id: 1, name: 'Jelena', status: 'active', role: 'main', lastSeen: '2024-02-19T13:10:00Z' },
|
||||
{ id: 2, name: 'Linus', status: 'active', role: 'cto', lastSeen: '2024-02-19T13:05:00Z' },
|
||||
{ id: 3, name: 'Neo', status: 'active', role: 'operator', lastSeen: '2024-02-19T13:15:00Z' }
|
||||
],
|
||||
whatsapp: {
|
||||
connected: true,
|
||||
lastMessage: '2024-02-19T13:05:00Z',
|
||||
unread: 3,
|
||||
groups: ['Team', 'Alerts', 'Support']
|
||||
},
|
||||
memory: {
|
||||
dailyNotes: 24,
|
||||
longTermEntries: 156,
|
||||
lastUpdated: '2024-02-19T12:30:00Z'
|
||||
}
|
||||
};
|
||||
|
||||
// API endpoint
|
||||
app.get('/api/data', (req, res) => {
|
||||
const type = req.query.type;
|
||||
|
||||
if (!type) {
|
||||
return res.status(400).json({ error: 'Missing type parameter' });
|
||||
}
|
||||
|
||||
if (mockData[type]) {
|
||||
return res.json(mockData[type]);
|
||||
}
|
||||
|
||||
return res.status(404).json({ error: `Unknown type: ${type}` });
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Simple API server running on port ${port}`);
|
||||
});
|
||||
Reference in New Issue
Block a user