rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61)
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61) Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
This commit was merged in pull request #61.
This commit is contained in:
@@ -2,7 +2,7 @@ import { HolidayCalendarEditor } from "~/components/vacations/HolidayCalendarEdi
|
||||
import { PublicHolidayBatch } from "~/components/vacations/PublicHolidayBatch.js";
|
||||
import { EntitlementManager } from "~/components/vacations/EntitlementManager.js";
|
||||
|
||||
export const metadata = { title: "Vacation Management — CapaKraken" };
|
||||
export const metadata = { title: "Vacation Management — Nexus" };
|
||||
|
||||
export default function AdminVacationsPage() {
|
||||
return (
|
||||
@@ -10,15 +10,19 @@ export default function AdminVacationsPage() {
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Vacation Management</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Verwalte Feiertagskalender pro Land, Bundesland und Stadt sowie Entitlements und Fallback-Importe.
|
||||
Verwalte Feiertagskalender pro Land, Bundesland und Stadt sowie Entitlements und
|
||||
Fallback-Importe.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Holiday Calendars</h2>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
|
||||
Holiday Calendars
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Fachliche Quelle fuer regionale Feiertage. Diese Kalender werden fuer Urlaubszaehlung, Timeline-Overlay und Assistant-Abfragen verwendet.
|
||||
Fachliche Quelle fuer regionale Feiertage. Diese Kalender werden fuer Urlaubszaehlung,
|
||||
Timeline-Overlay und Assistant-Abfragen verwendet.
|
||||
</p>
|
||||
</div>
|
||||
<HolidayCalendarEditor />
|
||||
@@ -26,9 +30,12 @@ export default function AdminVacationsPage() {
|
||||
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Legacy Batch Import</h2>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
|
||||
Legacy Batch Import
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Nur als Fallback fuer bestaende Prozesse. Bevorzugt sollen Feiertage ueber die Kalenderlogik und nicht als statische Urlaubseintraege gepflegt werden.
|
||||
Nur als Fallback fuer bestaende Prozesse. Bevorzugt sollen Feiertage ueber die
|
||||
Kalenderlogik und nicht als statische Urlaubseintraege gepflegt werden.
|
||||
</p>
|
||||
</div>
|
||||
<PublicHolidayBatch />
|
||||
@@ -36,9 +43,12 @@ export default function AdminVacationsPage() {
|
||||
|
||||
<section className="space-y-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">Entitlements</h2>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide text-gray-500">
|
||||
Entitlements
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Jahresansprueche und Resttage im gleichen Kontext pruefen, nachdem Feiertage regional aufgeloest wurden.
|
||||
Jahresansprueche und Resttage im gleichen Kontext pruefen, nachdem Feiertage regional
|
||||
aufgeloest wurden.
|
||||
</p>
|
||||
</div>
|
||||
<EntitlementManager />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { EstimateStatus, type EstimateVersionStatus } from "@capakraken/shared";
|
||||
import { EstimateStatus, type EstimateVersionStatus } from "@nexus/shared";
|
||||
import { clsx } from "clsx";
|
||||
import { EstimateWizard } from "~/components/estimates/EstimateWizard.js";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
@@ -122,7 +122,8 @@ function EstimateDetailPanel({
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-gray-400">
|
||||
Estimate detail <InfoTooltip content="Pre-project cost and effort calculation. Estimates model staffing demand, scope, and financials before work begins." />
|
||||
Estimate detail{" "}
|
||||
<InfoTooltip content="Pre-project cost and effort calculation. Estimates model staffing demand, scope, and financials before work begins." />
|
||||
</p>
|
||||
<h2 className="mt-2 text-xl font-semibold text-gray-900 dark:text-gray-50">
|
||||
{estimate.name}
|
||||
@@ -206,7 +207,8 @@ function EstimateDetailPanel({
|
||||
<section>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Scope items <InfoTooltip content="Deliverables or work packages that define what is included in this estimate." />
|
||||
Scope items{" "}
|
||||
<InfoTooltip content="Deliverables or work packages that define what is included in this estimate." />
|
||||
</h3>
|
||||
<span className="text-xs text-gray-400">{latestVersion.scopeItems.length}</span>
|
||||
</div>
|
||||
@@ -239,7 +241,8 @@ function EstimateDetailPanel({
|
||||
<section>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
Demand lines <InfoTooltip content="Staffing demand rows. Each line represents a role or resource with hours, cost rate, and sell rate." />
|
||||
Demand lines{" "}
|
||||
<InfoTooltip content="Staffing demand rows. Each line represents a role or resource with hours, cost rate, and sell rate." />
|
||||
</h3>
|
||||
<span className="text-xs text-gray-400">{latestVersion.demandLines.length}</span>
|
||||
</div>
|
||||
@@ -345,13 +348,19 @@ function EstimateCard({
|
||||
|
||||
<div className="mt-5 grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-wide text-gray-400">Opportunity <InfoTooltip content="External CRM or sales reference ID linking this estimate to a sales opportunity." /></p>
|
||||
<p className="text-xs uppercase tracking-wide text-gray-400">
|
||||
Opportunity{" "}
|
||||
<InfoTooltip content="External CRM or sales reference ID linking this estimate to a sales opportunity." />
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-gray-700 dark:text-gray-200">
|
||||
{estimate.opportunityId ?? "Not set"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-wide text-gray-400">Updated <InfoTooltip content="When this estimate or any of its versions was last modified." /></p>
|
||||
<p className="text-xs uppercase tracking-wide text-gray-400">
|
||||
Updated{" "}
|
||||
<InfoTooltip content="When this estimate or any of its versions was last modified." />
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-gray-700 dark:text-gray-200">
|
||||
{formatDateLong(estimate.updatedAt)}
|
||||
</p>
|
||||
@@ -466,7 +475,7 @@ export function EstimatesClient() {
|
||||
No estimates yet
|
||||
</p>
|
||||
<p className="mt-2 text-sm text-gray-400 dark:text-gray-500">
|
||||
Start with the wizard to create a connected estimate from CapaKraken data.
|
||||
Start with the wizard to create a connected estimate from Nexus data.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MobileSummaryClient } from "~/components/mobile/MobileSummaryClient.js";
|
||||
|
||||
export const metadata = {
|
||||
title: "CapaKraken — Mobile Summary",
|
||||
title: "Nexus — Mobile Summary",
|
||||
};
|
||||
|
||||
export default function MobilePage() {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useUrlFilters } from "~/hooks/useUrlFilters.js";
|
||||
import { useDebounce } from "~/hooks/useDebounce.js";
|
||||
import { createPortal } from "react-dom";
|
||||
import { formatDate, formatMoney } from "~/lib/format.js";
|
||||
import type { Project, ColumnDef, ProjectStatus } from "@capakraken/shared";
|
||||
import { PROJECT_COLUMNS, BlueprintTarget } from "@capakraken/shared";
|
||||
import type { Project, ColumnDef, ProjectStatus } from "@nexus/shared";
|
||||
import { PROJECT_COLUMNS, BlueprintTarget } from "@nexus/shared";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||
import { useUrlFilters } from "~/hooks/useUrlFilters.js";
|
||||
import { useDebounce } from "~/hooks/useDebounce.js";
|
||||
import Link from "next/link";
|
||||
import type { Resource, SkillEntry } from "@capakraken/shared";
|
||||
import { RESOURCE_COLUMNS } from "@capakraken/shared";
|
||||
import { BlueprintTarget, ResourceType } from "@capakraken/shared";
|
||||
import type { Resource, SkillEntry } from "@nexus/shared";
|
||||
import { RESOURCE_COLUMNS } from "@nexus/shared";
|
||||
import { BlueprintTarget, ResourceType } from "@nexus/shared";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
import { formatMoney } from "~/lib/format.js";
|
||||
import { generateCsv, downloadCsv } from "~/lib/csv-export.js";
|
||||
@@ -945,7 +945,7 @@ export function ResourcesClient() {
|
||||
sortField={sortField}
|
||||
sortDir={sortDir}
|
||||
onSort={toggle}
|
||||
tooltip="Unique employee identifier used across all CapaKraken records."
|
||||
tooltip="Unique employee identifier used across all Nexus records."
|
||||
/>
|
||||
);
|
||||
case "displayName":
|
||||
|
||||
@@ -2,24 +2,22 @@ import type { Metadata } from "next";
|
||||
import { createCaller } from "~/server/trpc.js";
|
||||
import { ResourceDetail } from "~/components/resources/ResourceDetail.js";
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
): Promise<Metadata> {
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { id } = await params;
|
||||
try {
|
||||
const trpc = await createCaller();
|
||||
const resource = await trpc.resource.getById({ id });
|
||||
return { title: `${resource.displayName} — Resources | CapaKraken` };
|
||||
return { title: `${resource.displayName} — Resources | Nexus` };
|
||||
} catch {
|
||||
return { title: "Resource — CapaKraken" };
|
||||
return { title: "Resource — Nexus" };
|
||||
}
|
||||
}
|
||||
|
||||
export default async function ResourceDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
export default async function ResourceDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
return <ResourceDetail resourceId={id} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Resource } from "@capakraken/shared";
|
||||
import { ResourceType } from "@capakraken/shared";
|
||||
import type { Resource } from "@nexus/shared";
|
||||
import { ResourceType } from "@nexus/shared";
|
||||
|
||||
export type ModalState =
|
||||
| { type: "closed" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MyVacationsClient } from "~/components/vacations/MyVacationsClient.js";
|
||||
|
||||
export const metadata = { title: "My Vacations — CapaKraken" };
|
||||
export const metadata = { title: "My Vacations — Nexus" };
|
||||
|
||||
export default function MyVacationsPage() {
|
||||
return <MyVacationsClient />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { prisma } from "@nexus/db";
|
||||
|
||||
/** Window over which auth events are analysed. */
|
||||
const WINDOW_MS = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
@@ -17,7 +17,7 @@ import { THRESHOLDS } from "./detect.js";
|
||||
const auditLogFindManyMock = vi.hoisted(() => vi.fn());
|
||||
const userFindManyMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@capakraken/db", () => ({
|
||||
vi.mock("@nexus/db", () => ({
|
||||
prisma: {
|
||||
auditLog: { findMany: auditLogFindManyMock },
|
||||
user: { findMany: userFindManyMock },
|
||||
@@ -27,11 +27,11 @@ vi.mock("@capakraken/db", () => ({
|
||||
// ─── createNotificationsForUsers mock ─────────────────────────────────────────
|
||||
const createNotificationsMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
|
||||
|
||||
vi.mock("@capakraken/api", () => ({
|
||||
vi.mock("@nexus/api", () => ({
|
||||
createNotificationsForUsers: createNotificationsMock,
|
||||
}));
|
||||
|
||||
vi.mock("@capakraken/api/lib/logger", () => ({
|
||||
vi.mock("@nexus/api/lib/logger", () => ({
|
||||
logger: { warn: vi.fn(), error: vi.fn(), info: vi.fn() },
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { createNotificationsForUsers } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { createNotificationsForUsers } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
import { detectAuthAnomalies } from "./detect.js";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { checkChargeabilityAlerts } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { checkChargeabilityAlerts } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { checkPendingEstimateReminders } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { checkPendingEstimateReminders } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { createNotificationsForUsers } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { createNotificationsForUsers } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { createConnection } from "net";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { autoImportPublicHolidays } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { autoImportPublicHolidays } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -45,10 +45,10 @@ export async function GET(request: Request) {
|
||||
skippedExisting: result.skippedExisting,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ error, route: "/api/cron/public-holidays", year }, "Public holiday import cron failed");
|
||||
return NextResponse.json(
|
||||
{ ok: false, error: "Internal error" },
|
||||
{ status: 500 },
|
||||
logger.error(
|
||||
{ error, route: "/api/cron/public-holidays", year },
|
||||
"Public holiday import cron failed",
|
||||
);
|
||||
return NextResponse.json({ ok: false, error: "Internal error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { createNotificationsForUsers } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { createNotificationsForUsers } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { sendWeeklyDigest } from "@capakraken/api";
|
||||
import { logger } from "@capakraken/api/lib/logger";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { sendWeeklyDigest } from "@nexus/api";
|
||||
import { logger } from "@nexus/api/lib/logger";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { createConnection } from "net";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -30,8 +30,14 @@ async function checkRedis(): Promise<"ok" | "error"> {
|
||||
socket.destroy();
|
||||
resolve(data.toString().includes("PONG") ? "ok" : "error");
|
||||
});
|
||||
socket.on("timeout", () => { socket.destroy(); resolve("error"); });
|
||||
socket.on("error", () => { socket.destroy(); resolve("error"); });
|
||||
socket.on("timeout", () => {
|
||||
socket.destroy();
|
||||
resolve("error");
|
||||
});
|
||||
socket.on("error", () => {
|
||||
socket.destroy();
|
||||
resolve("error");
|
||||
});
|
||||
} catch {
|
||||
resolve("error");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@capakraken/api/sse", () => ({
|
||||
vi.mock("@nexus/api/sse", () => ({
|
||||
eventBus: { subscriberCount: 0 },
|
||||
}));
|
||||
|
||||
@@ -33,7 +33,7 @@ describe("GET /api/perf — security hardening", () => {
|
||||
const response = await GET(request);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const body = await response.json() as { timestamp: string; uptime: unknown; memory: unknown };
|
||||
const body = (await response.json()) as { timestamp: string; uptime: unknown; memory: unknown };
|
||||
expect(typeof body.timestamp).toBe("string");
|
||||
expect(body.uptime).toBeDefined();
|
||||
expect(body.memory).toBeDefined();
|
||||
@@ -81,7 +81,11 @@ describe("GET /api/perf — security hardening", () => {
|
||||
const response = await GET(request);
|
||||
expect(response.status).toBe(401);
|
||||
|
||||
const body = await response.json() as { error?: string; timestamp?: string; memory?: unknown };
|
||||
const body = (await response.json()) as {
|
||||
error?: string;
|
||||
timestamp?: string;
|
||||
memory?: unknown;
|
||||
};
|
||||
expect(body.timestamp).toBeUndefined();
|
||||
expect(body.memory).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { eventBus } from "@capakraken/api/sse";
|
||||
import { eventBus } from "@nexus/api/sse";
|
||||
import { verifyCronSecret } from "~/lib/cron-auth.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { createConnection } from "net";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -18,7 +18,7 @@ async function checkPostgres(): Promise<"ok" | "error"> {
|
||||
|
||||
/**
|
||||
* Lightweight Redis PING check using a raw TCP socket.
|
||||
* Avoids importing ioredis (which is only a dependency of @capakraken/api).
|
||||
* Avoids importing ioredis (which is only a dependency of @nexus/api).
|
||||
*/
|
||||
async function checkRedis(): Promise<"ok" | "error"> {
|
||||
return new Promise((resolve) => {
|
||||
@@ -58,10 +58,7 @@ async function checkRedis(): Promise<"ok" | "error"> {
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const [postgres, redis] = await Promise.all([
|
||||
checkPostgres(),
|
||||
checkRedis(),
|
||||
]);
|
||||
const [postgres, redis] = await Promise.all([checkPostgres(), checkRedis()]);
|
||||
|
||||
const allHealthy = postgres === "ok" && redis === "ok";
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const authMock = vi.hoisted(() => vi.fn());
|
||||
vi.mock("~/server/auth.js", () => ({ auth: authMock }));
|
||||
|
||||
// ─── heavy dep stubs ─────────────────────────────────────────────────────────
|
||||
vi.mock("@capakraken/db", () => ({
|
||||
vi.mock("@nexus/db", () => ({
|
||||
prisma: {
|
||||
demandRequirement: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
assignment: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
@@ -21,11 +21,11 @@ vi.mock("@capakraken/db", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@capakraken/application", () => ({
|
||||
vi.mock("@nexus/application", () => ({
|
||||
buildSplitAllocationReadModel: vi.fn().mockReturnValue({ assignments: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("@capakraken/api", () => ({
|
||||
vi.mock("@nexus/api", () => ({
|
||||
anonymizeResource: vi.fn((r: unknown) => r),
|
||||
getAnonymizationDirectory: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
@@ -2,10 +2,10 @@ import { renderToBuffer } from "@react-pdf/renderer";
|
||||
import { createElement } from "react";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { buildSplitAllocationReadModel } from "@capakraken/application";
|
||||
import { anonymizeResource, getAnonymizationDirectory } from "@capakraken/api";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import type { AllocationLike } from "@capakraken/shared";
|
||||
import { buildSplitAllocationReadModel } from "@nexus/application";
|
||||
import { anonymizeResource, getAnonymizationDirectory } from "@nexus/api";
|
||||
import { prisma } from "@nexus/db";
|
||||
import type { AllocationLike } from "@nexus/shared";
|
||||
import { auth } from "~/server/auth.js";
|
||||
import { AllocationReport } from "~/components/reports/AllocationReport.js";
|
||||
import { createWorkbookArrayBuffer } from "~/lib/workbook-export.js";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { loadRoleDefaults } from "@capakraken/api";
|
||||
import { deriveUserSseSubscription, eventBus } from "@capakraken/api/sse";
|
||||
import { startReminderScheduler } from "@capakraken/api/lib/reminder-scheduler";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import type { SystemRole } from "@capakraken/shared";
|
||||
import { SSE_EVENT_TYPES, type PermissionOverrides } from "@capakraken/shared";
|
||||
import { loadRoleDefaults } from "@nexus/api";
|
||||
import { deriveUserSseSubscription, eventBus } from "@nexus/api/sse";
|
||||
import { startReminderScheduler } from "@nexus/api/lib/reminder-scheduler";
|
||||
import { prisma } from "@nexus/db";
|
||||
import type { SystemRole } from "@nexus/shared";
|
||||
import { SSE_EVENT_TYPES, type PermissionOverrides } from "@nexus/shared";
|
||||
import { auth } from "~/server/auth.js";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createTRPCContext, loadRoleDefaults } from "@capakraken/api";
|
||||
import { appRouter } from "@capakraken/api/router";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { createTRPCContext, loadRoleDefaults } from "@nexus/api";
|
||||
import { appRouter } from "@nexus/api/router";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { use, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@capakraken/shared";
|
||||
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
export default function ResetPasswordPage({ params }: { params: Promise<{ token: string }> }) {
|
||||
|
||||
@@ -98,7 +98,7 @@ export default function SignInPage() {
|
||||
<div className="hidden rounded-[2rem] border border-white/70 bg-white/75 p-10 shadow-2xl backdrop-blur lg:flex lg:flex-col lg:justify-between dark:border-slate-800 dark:bg-slate-950/60">
|
||||
<div>
|
||||
<span className="inline-flex rounded-full border border-brand-200 bg-brand-50 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-brand-700 dark:border-brand-900/50 dark:bg-brand-900/20 dark:text-brand-300">
|
||||
CapaKraken Control Center
|
||||
Nexus Control Center
|
||||
</span>
|
||||
<h1 className="mt-6 font-display text-5xl font-semibold leading-tight text-gray-900 dark:text-gray-50">
|
||||
Resource planning that stays readable under pressure.
|
||||
@@ -137,7 +137,7 @@ export default function SignInPage() {
|
||||
Welcome Back
|
||||
</p>
|
||||
<h2 className="mt-3 font-display text-4xl font-semibold text-gray-900 dark:text-gray-50">
|
||||
{mfaRequired ? "Two-Factor Authentication" : "Sign in to CapaKraken"}
|
||||
{mfaRequired ? "Two-Factor Authentication" : "Sign in to Nexus"}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
{mfaRequired
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, use } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@capakraken/shared";
|
||||
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
export default function AcceptInvitePage({ params }: { params: Promise<{ token: string }> }) {
|
||||
@@ -91,7 +91,7 @@ export default function AcceptInvitePage({ params }: { params: Promise<{ token:
|
||||
<div className="mb-6">
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">Accept invitation</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
You have been invited as <strong>{invite.role}</strong> to CapaKraken. Set a password to
|
||||
You have been invited as <strong>{invite.role}</strong> to Nexus. Set a password to
|
||||
activate your account (<span className="font-medium">{invite.email}</span>).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
+51
-10
@@ -19,8 +19,8 @@ const displayFont = Manrope({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://capakraken.hartmut-noerenberg.com"),
|
||||
title: "CapaKraken — Resource & Capacity Planning",
|
||||
metadataBase: new URL("https://nexus.hartmut-noerenberg.com"),
|
||||
title: "Nexus — Resource & Capacity Planning",
|
||||
description: "Interactive resource planning and project staffing tool",
|
||||
manifest: "/manifest.json",
|
||||
icons: {
|
||||
@@ -35,17 +35,17 @@ export const metadata: Metadata = {
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
statusBarStyle: "default",
|
||||
title: "CapaKraken",
|
||||
title: "Nexus",
|
||||
},
|
||||
openGraph: {
|
||||
title: "CapaKraken — Resource & Capacity Planning",
|
||||
title: "Nexus — Resource & Capacity Planning",
|
||||
description: "Estimates, staffing, chargeability, and timelines in one workspace.",
|
||||
images: [{ url: "/og-image.png", width: 1024, height: 1024, alt: "CapaKraken Logo" }],
|
||||
images: [{ url: "/og-image.png", width: 1024, height: 1024, alt: "Nexus Logo" }],
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "CapaKraken — Resource & Capacity Planning",
|
||||
title: "Nexus — Resource & Capacity Planning",
|
||||
description: "Estimates, staffing, chargeability, and timelines in one workspace.",
|
||||
images: ["/og-image.png"],
|
||||
},
|
||||
@@ -60,15 +60,56 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<script nonce={nonce} suppressHydrationWarning dangerouslySetInnerHTML={{__html: `
|
||||
<script
|
||||
nonce={nonce}
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
try {
|
||||
var p = JSON.parse(localStorage.getItem('capakraken_theme') || '{}');
|
||||
if (!localStorage.getItem('nexus_migrated_v1')) {
|
||||
var underscoreKeys = ['theme','sidebar_collapsed','mfa_prompt_snoozed_until','prefs','pwa_dismiss'];
|
||||
underscoreKeys.forEach(function(k){
|
||||
var oldK = 'capakraken_' + k, newK = 'nexus_' + k;
|
||||
var v = localStorage.getItem(oldK);
|
||||
if (v !== null && localStorage.getItem(newK) === null) localStorage.setItem(newK, v);
|
||||
localStorage.removeItem(oldK);
|
||||
});
|
||||
var dashKeys = [];
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
var lk = localStorage.key(i);
|
||||
if (lk && lk.indexOf('capakraken_dashboard_v1_') === 0) dashKeys.push(lk);
|
||||
}
|
||||
dashKeys.forEach(function(lk){
|
||||
var newLk = 'nexus_' + lk.substring('capakraken_'.length);
|
||||
var v = localStorage.getItem(lk);
|
||||
if (v !== null && localStorage.getItem(newLk) === null) localStorage.setItem(newLk, v);
|
||||
localStorage.removeItem(lk);
|
||||
});
|
||||
['capakraken-chat-messages','capakraken-chat-conversation-id'].forEach(function(lk){
|
||||
var newLk = 'nexus-' + lk.substring('capakraken-'.length);
|
||||
var v = localStorage.getItem(lk);
|
||||
if (v !== null && localStorage.getItem(newLk) === null) localStorage.setItem(newLk, v);
|
||||
localStorage.removeItem(lk);
|
||||
});
|
||||
var av = localStorage.getItem('capakraken:allocations:viewMode');
|
||||
if (av !== null && localStorage.getItem('nexus:allocations:viewMode') === null) {
|
||||
localStorage.setItem('nexus:allocations:viewMode', av);
|
||||
}
|
||||
localStorage.removeItem('capakraken:allocations:viewMode');
|
||||
localStorage.setItem('nexus_migrated_v1', '1');
|
||||
if (typeof caches !== 'undefined') caches.delete('capakraken-v2');
|
||||
}
|
||||
var p = JSON.parse(localStorage.getItem('nexus_theme') || '{}');
|
||||
if (p.mode === 'dark') document.documentElement.classList.add('dark');
|
||||
if (p.accent) document.documentElement.setAttribute('data-accent', p.accent);
|
||||
} catch(e) {}
|
||||
`}} />
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body className={`${uiFont.variable} ${displayFont.variable} min-h-screen bg-gray-50 font-sans antialiased`}>
|
||||
<body
|
||||
className={`${uiFont.variable} ${displayFont.variable} min-h-screen bg-gray-50 font-sans antialiased`}
|
||||
>
|
||||
<TRPCProvider>{children}</TRPCProvider>
|
||||
<ServiceWorkerRegistration />
|
||||
<InstallPrompt />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@capakraken/shared";
|
||||
import { PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
|
||||
import { createFirstAdmin } from "./actions.js";
|
||||
|
||||
export function SetupClient() {
|
||||
@@ -76,7 +76,7 @@ export function SetupClient() {
|
||||
<div className="mb-6">
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">First-run setup</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Create the initial administrator account for CapaKraken.
|
||||
Create the initial administrator account for Nexus.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
"use server";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { SystemRole } from "@capakraken/db";
|
||||
import {
|
||||
PASSWORD_MAX_LENGTH,
|
||||
PASSWORD_MIN_LENGTH,
|
||||
PASSWORD_POLICY_MESSAGE,
|
||||
} from "@capakraken/shared";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { SystemRole } from "@nexus/db";
|
||||
import { PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PASSWORD_POLICY_MESSAGE } from "@nexus/shared";
|
||||
|
||||
export type SetupResult =
|
||||
| { success: true }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { prisma } from "@capakraken/db";
|
||||
import { prisma } from "@nexus/db";
|
||||
import { SetupClient } from "./SetupClient.js";
|
||||
|
||||
export default async function SetupPage() {
|
||||
|
||||
Reference in New Issue
Block a user