Hetansh Waghela commited on
Commit
17af72c
·
1 Parent(s): 5466fed

Fix production demo auth and deployment layout

Browse files
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 --filter @courtmitra/api deploy --prod --legacy /deploy
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
- COPY --from=builder /deploy ./
 
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 ["npx", "tsx", "--tsconfig", "tsconfig.json", "src/main.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 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 = "demo";
34
- (request as any).userId = "demo";
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 ?? "demo";
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 = "demo-user-id";
22
- req.clerkUserId = "demo";
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("demo-user-id");
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
- export default createMiddleware(routing);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  export const config = {
7
- // Match all pathnames except:
8
- // - API routes (/api/*)
9
- // - Next.js internals (/_next/*, /_vercel/*)
10
- // - Static files (anything containing a dot)
11
- matcher: "/((?!api|_next|_vercel|.*\\..*).*)",
 
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
  };