Skip to content
Merged

Dev #115

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
2 changes: 2 additions & 0 deletions .github/workflows/admin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
run: |
pnpm --filter kal-shared build
pnpm --filter kal-baml build
pnpm --filter kal-backend build

- name: Lint
run: pnpm --filter kal-admin lint
Expand Down Expand Up @@ -90,6 +91,7 @@ jobs:
run: |
pnpm --filter kal-shared build
pnpm --filter kal-baml build
pnpm --filter kal-backend build

- name: Build admin
run: pnpm --filter kal-admin build
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
run: |
pnpm --filter kal-shared build
pnpm --filter kal-baml build
pnpm --filter kal-backend build

- name: Lint
run: pnpm --filter kal-frontend lint
Expand Down Expand Up @@ -90,6 +91,7 @@ jobs:
run: |
pnpm --filter kal-shared build
pnpm --filter kal-baml build
pnpm --filter kal-backend build

- name: Build frontend
run: pnpm --filter kal-frontend build
Expand Down
2 changes: 1 addition & 1 deletion packages/kal-admin/src/app/api/auth/login/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextRequest, NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";

const SESSION_COOKIE = "kal_admin_session";

Expand Down
2 changes: 1 addition & 1 deletion packages/kal-admin/src/app/api/trpc/[...trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextRequest, NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";

const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
const ADMIN_SECRET = process.env.ADMIN_SECRET ?? "";
Expand Down
2 changes: 0 additions & 2 deletions packages/kal-admin/src/app/dashboard/api-keys/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";

import { useState } from "react";
import { trpc } from "@/lib/trpc";
import { format } from "date-fns";

function StatCard({
label,
Expand Down
32 changes: 3 additions & 29 deletions packages/kal-admin/src/app/dashboard/feedback/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use client";

import { useMemo, useState } from "react";
import { trpc } from "@/lib/trpc";
import { format } from "date-fns";
import { useState } from "react";

import { trpc } from "@/lib/trpc";

type BugStatus = "open" | "in_progress" | "resolved" | "closed" | "wont_fix";
type BugStatusFilter = "all" | BugStatus;
Expand All @@ -27,33 +28,6 @@ function StarRating({ rating }: { rating: number }) {
);
}

function StatusBadge({ status }: { status: BugStatus }) {
const styles: Record<BugStatus, string> = {
open: "bg-status-warning/15 text-status-warning border-status-warning/20",
in_progress: "bg-status-info/15 text-status-info border-status-info/20",
resolved:
"bg-status-success/15 text-status-success border-status-success/20",
closed: "bg-slate-500/15 text-slate-400 border-slate-500/20",
wont_fix: "bg-status-danger/15 text-status-danger border-status-danger/20",
};

const labels: Record<BugStatus, string> = {
open: "Open",
in_progress: "In Progress",
resolved: "Resolved",
closed: "Closed",
wont_fix: "Won't Fix",
};

return (
<span
className={`inline-flex px-2 py-0.5 rounded text-xs font-medium border ${styles[status]}`}
>
{labels[status]}
</span>
);
}

function StatCard({
label,
value,
Expand Down
15 changes: 8 additions & 7 deletions packages/kal-admin/src/app/dashboard/logs/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import { useMemo, useState } from "react";
import {
Chart as ChartJS,
CategoryScale,
Expand All @@ -10,9 +9,11 @@ import {
Tooltip,
Legend,
} from "chart.js";
import { format } from "date-fns";
import { useMemo, useState } from "react";
import { Bar } from "react-chartjs-2";

import { trpc } from "@/lib/trpc";
import { format, formatDistanceToNow } from "date-fns";

ChartJS.register(
CategoryScale,
Expand Down Expand Up @@ -126,9 +127,9 @@ function StatusBadge({ statusCode }: { statusCode: number }) {
function LogsTable({
logs,
loading,
onPageChange,
currentPage,
pageSize,
onPageChange: _onPageChange,
currentPage: _currentPage,
pageSize: _pageSize,
}: {
logs: Array<{
_id?: string;
Expand Down Expand Up @@ -375,10 +376,10 @@ export default function LogsPage() {
endpointPrefix: "/api/v1/",
});

const isLoading = logsLoading || statsLoading;
const _isLoading = logsLoading || statsLoading;
const totalPages = logsData ? Math.ceil(logsData.total / limit) : 0;

const currentPreset =
const _currentPreset =
presetFilter === "api"
? "API"
: presetFilter === "health"
Expand Down
1 change: 1 addition & 0 deletions packages/kal-admin/src/app/dashboard/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useState } from "react";

import { trpc } from "@/lib/trpc";

type Tier = "free" | "tier_1" | "tier_2";
Expand Down
7 changes: 4 additions & 3 deletions packages/kal-admin/src/app/dashboard/users/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import { useMemo, useState } from "react";
import {
Chart as ChartJS,
CategoryScale,
Expand All @@ -10,9 +9,11 @@ import {
Tooltip,
Legend,
} from "chart.js";
import { formatDistanceToNow } from "date-fns";
import { useMemo, useState } from "react";
import { Bar } from "react-chartjs-2";

import { trpc } from "@/lib/trpc";
import { formatDistanceToNow } from "date-fns";

ChartJS.register(
CategoryScale,
Expand Down Expand Up @@ -310,7 +311,7 @@ export default function UsersPage() {
const { data: growth, isLoading: growthLoading } =
trpc.user.growth.useQuery();

const isLoading = usersLoading || statsLoading;
const _isLoading = usersLoading || statsLoading;

const filteredUsers = useMemo(() => {
if (!users) return [];
Expand Down
1 change: 1 addition & 0 deletions packages/kal-admin/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";

import "./globals.css";
import { TRPCProvider } from "@/lib/trpc-provider";

Expand Down
2 changes: 1 addition & 1 deletion packages/kal-admin/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState, FormEvent } from "react";
import { useRouter } from "next/navigation";
import { useState, type FormEvent } from "react";

export default function LoginPage() {
const router = useRouter();
Expand Down
2 changes: 1 addition & 1 deletion packages/kal-admin/src/lib/trpc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createTRPCReact } from "@trpc/react-query";
import type { CreateTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "kal-backend/src/routers";
import type { AppRouter } from "kal-backend";

export const trpc: CreateTRPCReact<AppRouter, unknown> =
createTRPCReact<AppRouter>();
4 changes: 2 additions & 2 deletions packages/kal-admin/vercel.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"framework": "nextjs",
"installCommand": "pnpm install",
"buildCommand": "pnpm build",
"installCommand": "cd ../.. && pnpm install --frozen-lockfile",
"buildCommand": "cd ../.. && pnpm turbo build --filter=kal-admin...",
"outputDirectory": ".next"
}
7 changes: 7 additions & 0 deletions packages/kal-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/routers/index.d.ts",
"exports": {
".": {
"types": "./dist/routers/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
Expand Down
4 changes: 1 addition & 3 deletions packages/kal-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import cookieParser from "cookie-parser";
import cors from "cors";
import express from "express";
import session from "express-session";
import swaggerUi from "swagger-ui-express";

import helmet from "helmet";

import { API_BASE_PATH } from "kal-shared";
import swaggerUi from "swagger-ui-express";

import { createContext } from "./lib/context.js";
import { connectDB } from "./lib/db.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/kal-backend/src/lib/cache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getRedis, isRedisAvailable } from "./redis.js";
import { logger } from "./logger.js";
import { getRedis, isRedisAvailable } from "./redis.js";

/**
* Cache service providing high-level caching operations
Expand Down
43 changes: 28 additions & 15 deletions packages/kal-backend/src/lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ async function syncUserFromLogto(

const now = new Date();
const usersCollection = db.collection<User>("users");

// Use name, username, or email (before @) as display name
const displayName = claims.name || claims.username || claims.email?.split("@")[0] || "";
const displayName =
claims.name || claims.username || claims.email?.split("@")[0] || "";

// Upsert user - create if not exists, update if exists
const result = await usersCollection.findOneAndUpdate(
Expand Down Expand Up @@ -76,15 +77,21 @@ export async function createContext({
// ------------------------------------------------------------------------
// VIRTUAL ADMIN AUTH (Env-Based for Open Source/Bootstrap)
// ------------------------------------------------------------------------
const adminSecretHeader = req.headers["x-admin-secret"] as string | undefined;
const adminSecretHeader = req.headers["x-admin-secret"] as
| string
| undefined;
const envAdminSecret = process.env.ADMIN_SECRET;

if (adminSecretHeader && envAdminSecret && adminSecretHeader === envAdminSecret) {
if (
adminSecretHeader &&
envAdminSecret &&
adminSecretHeader === envAdminSecret
) {
// Create a virtual super admin in memory (no DB access needed)
// We use a predefined ID and full permissions
const { ObjectId } = await import("mongodb");
user = {
_id: new ObjectId("000000000000000000000000") as any, // Fixed Virtual ID
_id: new ObjectId("000000000000000000000000") as unknown as User["_id"], // Fixed Virtual ID
logtoId: "admin-virtual-account",
email: "admin@system.local",
name: "System Administrator",
Expand All @@ -108,9 +115,13 @@ export async function createContext({
// Try Logto ID from frontend header (when using Next.js proxy/tRPC)
const headerLogtoId = req.headers["x-logto-id"] as string | undefined;
if (headerLogtoId) {
user = await db.collection<User>("users").findOne({ logtoId: headerLogtoId });

const headerEmail = req.headers["x-logto-email"] as string | undefined;
user = await db
.collection<User>("users")
.findOne({ logtoId: headerLogtoId });

const headerEmail = req.headers["x-logto-email"] as
| string
| undefined;
const headerName = req.headers["x-logto-name"] as string | undefined;

// If user not found but we have claims in headers (trusted from frontend), create/sync them
Expand All @@ -124,13 +135,15 @@ export async function createContext({
});
}
} else if (headerName && !user.name) {
// Update if local name is missing but header has one
await db.collection<User>("users").updateOne(
{ _id: user._id },
{ $set: { name: headerName, email: headerEmail || user.email } }
);
user.name = headerName;
if (headerEmail) user.email = headerEmail;
// Update if local name is missing but header has one
await db
.collection<User>("users")
.updateOne(
{ _id: user._id },
{ $set: { name: headerName, email: headerEmail || user.email } }
);
user.name = headerName;
if (headerEmail) user.email = headerEmail;
}
}

Expand Down
19 changes: 12 additions & 7 deletions packages/kal-backend/src/lib/platform-settings.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import { RATE_LIMITS, type RateLimitConfig, type UserTier } from "kal-shared";
import type { Db, Filter, Document } from "mongodb";

import { cache } from "./cache.js";
import type { Db } from "mongodb";

const SETTINGS_ID = "rate_limits";
const CACHE_KEY = "platform:rate_limits";
const CACHE_TTL = 60 * 5; // 5 minutes cache for middleware to pick up

// Helper to get effective limits (DB > Default)
export async function getEffectiveRateLimits(db: Db): Promise<Record<UserTier, RateLimitConfig>> {
export async function getEffectiveRateLimits(
db: Db
): Promise<Record<UserTier, RateLimitConfig>> {
// Try cache first
const cached = await cache.get<Record<UserTier, RateLimitConfig>>(CACHE_KEY);
if (cached) return cached;

// Try DB
// Note: db.collection might throw if db is not connected, but assumed connected here
const doc = await db.collection("platform_settings").findOne({ _id: SETTINGS_ID as any });

const doc = await db
.collection("platform_settings")
.findOne({ _id: SETTINGS_ID } as unknown as Filter<Document>);

let merged = { ...RATE_LIMITS };

if (doc && doc.limits) {
// Merge DB limits with defaults
// We'll replace per tier if exists in DB to override code defaults
merged = {
...merged,
...doc.limits
...merged,
...doc.limits,
};
}

Expand All @@ -33,5 +38,5 @@ export async function getEffectiveRateLimits(db: Db): Promise<Record<UserTier, R
}

export function invalidateRateLimitsCache() {
cache.del(CACHE_KEY);
cache.del(CACHE_KEY);
}
1 change: 1 addition & 0 deletions packages/kal-backend/src/lib/wsServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { applyWSSHandler } from '@trpc/server/adapters/ws';
import { WebSocketServer } from 'ws';

import { appRouter } from '../routers/index.js';

import { createWsContext } from './wsContext.js';

const WS_PORT = parseInt(process.env.WS_PORT || '3001', 10);
Expand Down
4 changes: 2 additions & 2 deletions packages/kal-backend/src/middleware/cache-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Request, Response, NextFunction } from "express";

import { CacheKeys, CacheTTL } from "../lib/cache-keys.js";
import { cache } from "../lib/cache.js";
import { isRedisAvailable } from "../lib/redis.js";
import { logger } from "../lib/logger.js";
import { CacheKeys, CacheTTL } from "../lib/cache-keys.js";
import { isRedisAvailable } from "../lib/redis.js";

/**
* Response wrapper to capture response body for caching
Expand Down
Loading
Loading