Spaces:
Sleeping
Sleeping
Hetansh Waghela commited on
Commit ·
17af72c
1
Parent(s): 5466fed
Fix production demo auth and deployment layout
Browse files- Dockerfile +4 -6
- apps/api/src/auth/clerk-auth.guard.ts +3 -2
- apps/api/src/auth/current-user.decorator.ts +2 -1
- apps/api/src/auth/demo-user.ts +3 -0
- apps/api/src/main.ts +20 -3
- apps/web/middleware.ts +0 -30
- apps/web/src/middleware.ts +22 -6
Dockerfile
CHANGED
|
@@ -25,18 +25,16 @@ COPY packages/db/drizzle.config.ts packages/db/drizzle.config.ts
|
|
| 25 |
COPY apps/api/tsconfig.json apps/api/tsconfig.json
|
| 26 |
COPY apps/worker/tsconfig.json apps/worker/tsconfig.json
|
| 27 |
|
| 28 |
-
RUN pnpm
|
| 29 |
-
COPY apps/api/tsconfig.json /deploy/tsconfig.json
|
| 30 |
-
|
| 31 |
-
RUN rm -rf /deploy/node_modules/.pnpm/next@* /deploy/node_modules/.pnpm/@next+* /deploy/node_modules/.pnpm/typescript@* /deploy/node_modules/.pnpm/caniuse-lite@* /deploy/node_modules/.pnpm/react@* /deploy/node_modules/.pnpm/react-dom@* /deploy/node_modules/.pnpm/web-streams-polyfill@*
|
| 32 |
|
| 33 |
FROM node:23-alpine AS runner
|
| 34 |
RUN apk add --no-cache tini curl
|
| 35 |
WORKDIR /app
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
EXPOSE 7860
|
| 39 |
ENV API_PORT=7860 PORT=7860
|
| 40 |
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD curl -f http://localhost:7860/api/admin/health || exit 1
|
| 41 |
ENTRYPOINT ["/sbin/tini", "--"]
|
| 42 |
-
CMD ["
|
|
|
|
| 25 |
COPY apps/api/tsconfig.json apps/api/tsconfig.json
|
| 26 |
COPY apps/worker/tsconfig.json apps/worker/tsconfig.json
|
| 27 |
|
| 28 |
+
RUN pnpm prune --prod
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
FROM node:23-alpine AS runner
|
| 31 |
RUN apk add --no-cache tini curl
|
| 32 |
WORKDIR /app
|
| 33 |
+
RUN corepack enable && corepack prepare pnpm@10 --activate
|
| 34 |
+
COPY --from=builder /app ./
|
| 35 |
|
| 36 |
EXPOSE 7860
|
| 37 |
ENV API_PORT=7860 PORT=7860
|
| 38 |
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD curl -f http://localhost:7860/api/admin/health || exit 1
|
| 39 |
ENTRYPOINT ["/sbin/tini", "--"]
|
| 40 |
+
CMD ["pnpm", "--filter", "@courtmitra/api", "start:prod"]
|
apps/api/src/auth/clerk-auth.guard.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Reflector } from "@nestjs/core";
|
|
| 3 |
import { verifyToken } from "@clerk/clerk-sdk-node";
|
| 4 |
import { AuthService } from "./auth.service.js";
|
| 5 |
import { IS_PUBLIC_KEY } from "./public.decorator.js";
|
|
|
|
| 6 |
|
| 7 |
@Injectable()
|
| 8 |
export class ClerkAuthGuard implements CanActivate {
|
|
@@ -30,8 +31,8 @@ export class ClerkAuthGuard implements CanActivate {
|
|
| 30 |
// Demo mode fallback — allow through with a demo user context
|
| 31 |
// This lets local dev work without Clerk keys
|
| 32 |
if (process.env.DEMO_MODE === "true") {
|
| 33 |
-
(request as any).clerkUserId =
|
| 34 |
-
(request as any).userId =
|
| 35 |
return true;
|
| 36 |
}
|
| 37 |
throw new UnauthorizedException("Missing Authorization header");
|
|
|
|
| 3 |
import { verifyToken } from "@clerk/clerk-sdk-node";
|
| 4 |
import { AuthService } from "./auth.service.js";
|
| 5 |
import { IS_PUBLIC_KEY } from "./public.decorator.js";
|
| 6 |
+
import { DEMO_CLERK_USER_ID, DEMO_USER_ID } from "./demo-user.js";
|
| 7 |
|
| 8 |
@Injectable()
|
| 9 |
export class ClerkAuthGuard implements CanActivate {
|
|
|
|
| 31 |
// Demo mode fallback — allow through with a demo user context
|
| 32 |
// This lets local dev work without Clerk keys
|
| 33 |
if (process.env.DEMO_MODE === "true") {
|
| 34 |
+
(request as any).clerkUserId = DEMO_CLERK_USER_ID;
|
| 35 |
+
(request as any).userId = DEMO_USER_ID;
|
| 36 |
return true;
|
| 37 |
}
|
| 38 |
throw new UnauthorizedException("Missing Authorization header");
|
apps/api/src/auth/current-user.decorator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
|
|
|
| 2 |
|
| 3 |
/**
|
| 4 |
* Injects the DB userId from the request (set by ClerkAuthGuard).
|
|
@@ -7,6 +8,6 @@ import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
|
| 7 |
export const CurrentUser = createParamDecorator(
|
| 8 |
(_data: unknown, ctx: ExecutionContext): string => {
|
| 9 |
const request = ctx.switchToHttp().getRequest<{ userId?: string }>();
|
| 10 |
-
return request.userId ??
|
| 11 |
}
|
| 12 |
);
|
|
|
|
| 1 |
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
|
| 2 |
+
import { DEMO_USER_ID } from "./demo-user.js";
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Injects the DB userId from the request (set by ClerkAuthGuard).
|
|
|
|
| 8 |
export const CurrentUser = createParamDecorator(
|
| 9 |
(_data: unknown, ctx: ExecutionContext): string => {
|
| 10 |
const request = ctx.switchToHttp().getRequest<{ userId?: string }>();
|
| 11 |
+
return request.userId ?? DEMO_USER_ID;
|
| 12 |
}
|
| 13 |
);
|
apps/api/src/auth/demo-user.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const DEMO_USER_ID = "00000000-0000-0000-0000-000000000001";
|
| 2 |
+
export const DEMO_CLERK_USER_ID = "demo";
|
| 3 |
+
|
apps/api/src/main.ts
CHANGED
|
@@ -6,6 +6,21 @@ import { serve } from "inngest/node";
|
|
| 6 |
import { inngest as workerInngest, functions } from "@courtmitra/worker";
|
| 7 |
import { getCapabilities } from "@courtmitra/adapters";
|
| 8 |
import { MissionsService } from "./missions/missions.service.js";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
async function bootstrap() {
|
| 11 |
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(), {
|
|
@@ -17,9 +32,11 @@ async function bootstrap() {
|
|
| 17 |
const fastify = app.getHttpAdapter().getInstance();
|
| 18 |
|
| 19 |
if (process.env.DEMO_MODE === "true") {
|
|
|
|
|
|
|
| 20 |
fastify.addHook("onRequest", async (req: any) => {
|
| 21 |
-
req.userId =
|
| 22 |
-
req.clerkUserId =
|
| 23 |
});
|
| 24 |
}
|
| 25 |
|
|
@@ -29,7 +46,7 @@ async function bootstrap() {
|
|
| 29 |
const missions = app.get(MissionsService);
|
| 30 |
fastify.get("/api/diag/missions", async () => {
|
| 31 |
try {
|
| 32 |
-
const result = await missions.list(
|
| 33 |
return { ok: true, count: result.length };
|
| 34 |
} catch (e: any) {
|
| 35 |
return { ok: false, message: e.message, cause: e.cause?.message || e.cause?.code };
|
|
|
|
| 6 |
import { inngest as workerInngest, functions } from "@courtmitra/worker";
|
| 7 |
import { getCapabilities } from "@courtmitra/adapters";
|
| 8 |
import { MissionsService } from "./missions/missions.service.js";
|
| 9 |
+
import { schema, type Database } from "@courtmitra/db";
|
| 10 |
+
import { DB } from "./db/db.module.js";
|
| 11 |
+
import { DEMO_CLERK_USER_ID, DEMO_USER_ID } from "./auth/demo-user.js";
|
| 12 |
+
|
| 13 |
+
async function ensureDemoUser(db: Database) {
|
| 14 |
+
await db
|
| 15 |
+
.insert(schema.users)
|
| 16 |
+
.values({
|
| 17 |
+
id: DEMO_USER_ID,
|
| 18 |
+
displayName: "Demo User",
|
| 19 |
+
email: "demo@courtmitra.local",
|
| 20 |
+
fullName: "Demo User",
|
| 21 |
+
})
|
| 22 |
+
.onConflictDoNothing();
|
| 23 |
+
}
|
| 24 |
|
| 25 |
async function bootstrap() {
|
| 26 |
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(), {
|
|
|
|
| 32 |
const fastify = app.getHttpAdapter().getInstance();
|
| 33 |
|
| 34 |
if (process.env.DEMO_MODE === "true") {
|
| 35 |
+
await ensureDemoUser(app.get<Database>(DB));
|
| 36 |
+
|
| 37 |
fastify.addHook("onRequest", async (req: any) => {
|
| 38 |
+
req.userId = DEMO_USER_ID;
|
| 39 |
+
req.clerkUserId = DEMO_CLERK_USER_ID;
|
| 40 |
});
|
| 41 |
}
|
| 42 |
|
|
|
|
| 46 |
const missions = app.get(MissionsService);
|
| 47 |
fastify.get("/api/diag/missions", async () => {
|
| 48 |
try {
|
| 49 |
+
const result = await missions.list(DEMO_USER_ID);
|
| 50 |
return { ok: true, count: result.length };
|
| 51 |
} catch (e: any) {
|
| 52 |
return { ok: false, message: e.message, cause: e.cause?.message || e.cause?.code };
|
apps/web/middleware.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
|
| 2 |
-
import createMiddleware from "next-intl/middleware";
|
| 3 |
-
import { routing } from "./src/i18n/routing";
|
| 4 |
-
|
| 5 |
-
const intlMiddleware = createMiddleware(routing);
|
| 6 |
-
|
| 7 |
-
const isProtectedRoute = createRouteMatcher([
|
| 8 |
-
"/:locale/ledger",
|
| 9 |
-
"/:locale/missions(.*)",
|
| 10 |
-
"/:locale/profile",
|
| 11 |
-
]);
|
| 12 |
-
|
| 13 |
-
export default clerkMiddleware(async (auth, req) => {
|
| 14 |
-
// Protect specific routes
|
| 15 |
-
if (isProtectedRoute(req)) {
|
| 16 |
-
await auth.protect();
|
| 17 |
-
}
|
| 18 |
-
|
| 19 |
-
// Always run next-intl middleware for locale handling
|
| 20 |
-
return intlMiddleware(req);
|
| 21 |
-
});
|
| 22 |
-
|
| 23 |
-
export const config = {
|
| 24 |
-
matcher: [
|
| 25 |
-
// Skip Next.js internals and all static files
|
| 26 |
-
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
|
| 27 |
-
// Always run for API routes
|
| 28 |
-
"/(api|trpc)(.*)",
|
| 29 |
-
],
|
| 30 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
apps/web/src/middleware.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
|
|
| 1 |
import createMiddleware from "next-intl/middleware";
|
| 2 |
import { routing } from "./i18n/routing";
|
| 3 |
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
export const config = {
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
| 12 |
};
|
|
|
|
| 1 |
+
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
|
| 2 |
import createMiddleware from "next-intl/middleware";
|
| 3 |
import { routing } from "./i18n/routing";
|
| 4 |
|
| 5 |
+
const intlMiddleware = createMiddleware(routing);
|
| 6 |
+
|
| 7 |
+
const isProtectedRoute = createRouteMatcher([
|
| 8 |
+
"/:locale/ledger",
|
| 9 |
+
"/:locale/missions(.*)",
|
| 10 |
+
"/:locale/profile",
|
| 11 |
+
]);
|
| 12 |
+
|
| 13 |
+
export default clerkMiddleware(async (auth, req) => {
|
| 14 |
+
if (isProtectedRoute(req)) {
|
| 15 |
+
await auth.protect();
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
return intlMiddleware(req);
|
| 19 |
+
});
|
| 20 |
|
| 21 |
export const config = {
|
| 22 |
+
matcher: [
|
| 23 |
+
// Skip Next.js internals and static files, but keep app routes under
|
| 24 |
+
// Clerk + next-intl middleware.
|
| 25 |
+
"/((?!_next|_vercel|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
|
| 26 |
+
"/(api|trpc)(.*)",
|
| 27 |
+
],
|
| 28 |
};
|