feat: add production deployment config

- Add docker-compose.prod.yml and Caddyfile for Caddy reverse proxy
- Add production stages to frontend Dockerfile (nginx for static files)
- Fix monorepo package exports for production builds (dist/src paths)
- Add CORS_ORIGIN env var for cross-origin config
- Add Better Auth baseURL, cookie domain, and trusted origins from env
- Use VITE_API_URL for API calls in auth-client and play route
- Add credentials: include for cross-origin fetch requests
- Remove unused users table from schema
This commit is contained in:
lila 2026-04-14 11:38:40 +02:00
parent 3f7bc4111e
commit bc38137a12
20 changed files with 421515 additions and 34 deletions

View file

@ -17,3 +17,20 @@ COPY --from=deps /app/node_modules ./node_modules
COPY . ./
EXPOSE 5173
CMD ["pnpm", "--filter", "web", "dev", "--host"]
# 4. Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm install
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN pnpm --filter shared build
RUN pnpm --filter web build
# 5. Production — just nginx serving static files
FROM nginx:alpine AS production
COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

9
apps/web/nginx.conf Normal file
View file

@ -0,0 +1,9 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

View file

@ -1,7 +1,7 @@
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: "http://localhost:3000",
baseURL: import.meta.env["VITE_API_URL"] || "http://localhost:3000",
});
export const { signIn, signOut, useSession } = authClient;

View file

@ -20,7 +20,7 @@ const LoginPage = () => {
onClick={() =>
signIn.social({
provider: "github",
callbackURL: "http://localhost:5173",
callbackURL: window.location.origin,
})
}
>
@ -31,7 +31,7 @@ const LoginPage = () => {
onClick={() =>
signIn.social({
provider: "google",
callbackURL: "http://localhost:5173",
callbackURL: window.location.origin,
})
}
>

View file

@ -7,6 +7,8 @@ import { GameSetup } from "../components/game/GameSetup";
import { authClient } from "../lib/auth-client";
function Play() {
const API_URL = import.meta.env["VITE_API_URL"] || "";
const [gameSession, setGameSession] = useState<GameSession | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
@ -15,9 +17,10 @@ function Play() {
const startGame = useCallback(async (settings: GameRequest) => {
setIsLoading(true);
const response = await fetch("/api/v1/game/start", {
const response = await fetch(`${API_URL}/api/v1/game/start`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(settings),
});
const data = await response.json();
@ -42,9 +45,10 @@ function Play() {
const question = gameSession.questions[currentQuestionIndex];
if (!question) return;
const response = await fetch("/api/v1/game/answer", {
const response = await fetch(`${API_URL}/api/v1/game/answer`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
sessionId: gameSession.sessionId,
questionId: question.questionId,