fix(api): add global /api prefix to resolve frontend route mismatch
Some checks failed
ci/woodpecker/push/api Pipeline failed
Some checks failed
ci/woodpecker/push/api Pipeline failed
Frontend API client prefixes all paths with /api/ (e.g. /api/projects,
/api/knowledge/entries) but backend controllers had no global prefix,
causing 404s on all data-fetching pages in production.
- Add setGlobalPrefix('api') in main.ts with exclusions for /health
(Docker healthcheck) and /auth/* (BetterAuth OAuth flow)
- Strip redundant 'api/' from federation and CSRF controller paths
that already included the prefix manually
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ interface AuthenticatedRequest extends Request {
|
|||||||
user?: AuthenticatedUser;
|
user?: AuthenticatedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Controller("api/v1/csrf")
|
@Controller("v1/csrf")
|
||||||
export class CsrfController {
|
export class CsrfController {
|
||||||
constructor(private readonly csrfService: CsrfService) {}
|
constructor(private readonly csrfService: CsrfService) {}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { AuthenticatedRequest } from "../common/types/user.types";
|
|||||||
import type { CommandMessageDetails, CommandResponse } from "./types/message.types";
|
import type { CommandMessageDetails, CommandResponse } from "./types/message.types";
|
||||||
import type { FederationMessageStatus } from "@prisma/client";
|
import type { FederationMessageStatus } from "@prisma/client";
|
||||||
|
|
||||||
@Controller("api/v1/federation")
|
@Controller("v1/federation")
|
||||||
export class CommandController {
|
export class CommandController {
|
||||||
private readonly logger = new Logger(CommandController.name);
|
private readonly logger = new Logger(CommandController.name);
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
IncomingEventAckDto,
|
IncomingEventAckDto,
|
||||||
} from "./dto/event.dto";
|
} from "./dto/event.dto";
|
||||||
|
|
||||||
@Controller("api/v1/federation")
|
@Controller("v1/federation")
|
||||||
export class EventController {
|
export class EventController {
|
||||||
private readonly logger = new Logger(EventController.name);
|
private readonly logger = new Logger(EventController.name);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
ValidateFederatedTokenDto,
|
ValidateFederatedTokenDto,
|
||||||
} from "./dto/federated-auth.dto";
|
} from "./dto/federated-auth.dto";
|
||||||
|
|
||||||
@Controller("api/v1/federation/auth")
|
@Controller("v1/federation/auth")
|
||||||
export class FederationAuthController {
|
export class FederationAuthController {
|
||||||
private readonly logger = new Logger(FederationAuthController.name);
|
private readonly logger = new Logger(FederationAuthController.name);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
} from "./dto/connection.dto";
|
} from "./dto/connection.dto";
|
||||||
import { FederationConnectionStatus } from "@prisma/client";
|
import { FederationConnectionStatus } from "@prisma/client";
|
||||||
|
|
||||||
@Controller("api/v1/federation")
|
@Controller("v1/federation")
|
||||||
export class FederationController {
|
export class FederationController {
|
||||||
private readonly logger = new Logger(FederationController.name);
|
private readonly logger = new Logger(FederationController.name);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { AuthenticatedRequest } from "../common/types/user.types";
|
|||||||
import type { QueryMessageDetails, QueryResponse } from "./types/message.types";
|
import type { QueryMessageDetails, QueryResponse } from "./types/message.types";
|
||||||
import type { FederationMessageStatus } from "@prisma/client";
|
import type { FederationMessageStatus } from "@prisma/client";
|
||||||
|
|
||||||
@Controller("api/v1/federation")
|
@Controller("v1/federation")
|
||||||
export class QueryController {
|
export class QueryController {
|
||||||
private readonly logger = new Logger(QueryController.name);
|
private readonly logger = new Logger(QueryController.name);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NestFactory } from "@nestjs/core";
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { ValidationPipe } from "@nestjs/common";
|
import { RequestMethod, ValidationPipe } from "@nestjs/common";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import { AppModule } from "./app.module";
|
import { AppModule } from "./app.module";
|
||||||
import { getTrustedOrigins } from "./auth/auth.config";
|
import { getTrustedOrigins } from "./auth/auth.config";
|
||||||
@@ -47,6 +47,16 @@ async function bootstrap() {
|
|||||||
|
|
||||||
app.useGlobalFilters(new GlobalExceptionFilter());
|
app.useGlobalFilters(new GlobalExceptionFilter());
|
||||||
|
|
||||||
|
// Set global API prefix — all routes get /api/* except auth and health
|
||||||
|
// Auth routes are excluded because BetterAuth expects /auth/* paths
|
||||||
|
// Health is excluded because Docker healthchecks hit /health directly
|
||||||
|
app.setGlobalPrefix("api", {
|
||||||
|
exclude: [
|
||||||
|
{ path: "health", method: RequestMethod.GET },
|
||||||
|
{ path: "auth/(.*)", method: RequestMethod.ALL },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
// Configure CORS for cookie-based authentication
|
// Configure CORS for cookie-based authentication
|
||||||
// Origin list is shared with BetterAuth trustedOrigins via getTrustedOrigins()
|
// Origin list is shared with BetterAuth trustedOrigins via getTrustedOrigins()
|
||||||
const trustedOrigins = getTrustedOrigins();
|
const trustedOrigins = getTrustedOrigins();
|
||||||
|
|||||||
Reference in New Issue
Block a user