diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..20a36be --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM rust:1.92 AS builder + +WORKDIR /app +COPY . . + +# Build the release binary +RUN cargo build --release -p notes-api + +FROM debian:bookworm-slim + +WORKDIR /app + +# Install OpenSSL (required for many Rust networking crates) and CA certificates +RUN apt-get update && apt-get install -y libssl3 ca-certificates && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/target/release/notes-api . + + +# Create data directory for SQLite +RUN mkdir -p /app/data + +ENV DATABASE_URL=sqlite:///app/data/notes.db +ENV SESSION_SECRET=supersecretchangeinproduction + +EXPOSE 3000 + +CMD ["./notes-api"] diff --git a/compose.yml b/compose.yml index 14581ec..bad0ae3 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,28 @@ -#services: -# api: -# db: +services: + backend: + build: . + ports: + - "3000:3000" + environment: + # In production, use a secure secret + - SESSION_SECRET=dev_secret_key_12345 + - DATABASE_URL=sqlite:///app/data/notes.db + - CORS_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5173 + - HOST=0.0.0.0 + - PORT=3000 + volumes: + - ./data:/app/data -#volumes: -# db_volume: \ No newline at end of file + frontend: + build: ./k-notes-frontend + ports: + - "8080:80" + environment: + # This sets the default backend URL for the frontend + - API_URL=http://localhost:3000 + depends_on: + - backend + +# Optional: Define volumes explicitly if needed +# volumes: +# backend_data: diff --git a/k-notes-frontend/Dockerfile b/k-notes-frontend/Dockerfile new file mode 100644 index 0000000..0ad66b1 --- /dev/null +++ b/k-notes-frontend/Dockerfile @@ -0,0 +1,29 @@ +# Build stage +FROM oven/bun:1 AS builder + +WORKDIR /app +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +COPY . . +RUN bun run build + +# Production stage +FROM nginx:alpine + +# Copy built assets +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Create script to generate env-config.js from environment variables +RUN echo '#!/bin/sh' > /docker-entrypoint.d/40-env-config.sh && \ + echo 'echo "window.env = {" > /usr/share/nginx/html/env-config.js' >> /docker-entrypoint.d/40-env-config.sh && \ + echo 'if [ -n "$API_URL" ]; then echo " API_URL: \"$API_URL\"," >> /usr/share/nginx/html/env-config.js; fi' >> /docker-entrypoint.d/40-env-config.sh && \ + echo 'echo "};" >> /usr/share/nginx/html/env-config.js' >> /docker-entrypoint.d/40-env-config.sh && \ + chmod +x /docker-entrypoint.d/40-env-config.sh + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/k-notes-frontend/index.html b/k-notes-frontend/index.html index cf55f38..b0b7b20 100644 --- a/k-notes-frontend/index.html +++ b/k-notes-frontend/index.html @@ -5,6 +5,7 @@ + K-Notes diff --git a/k-notes-frontend/nginx.conf b/k-notes-frontend/nginx.conf new file mode 100644 index 0000000..9bf87cc --- /dev/null +++ b/k-notes-frontend/nginx.conf @@ -0,0 +1,14 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # Optional: Proxy API requests if using same domain (not needed for this specific setup but good to have) + # location /api { + # proxy_pass http://backend:3000; + # } +} diff --git a/k-notes-frontend/src/lib/api.ts b/k-notes-frontend/src/lib/api.ts index 580066e..c88a844 100644 --- a/k-notes-frontend/src/lib/api.ts +++ b/k-notes-frontend/src/lib/api.ts @@ -1,9 +1,29 @@ +declare global { + interface Window { + env?: { + API_URL?: string; + }; + } +} + const getApiUrl = () => { + // 1. Runtime config (Docker) + if (window.env?.API_URL) { + return `${window.env.API_URL}/api/v1`; + } + // 2. LocalStorage override const stored = localStorage.getItem("k_notes_api_url"); - return stored ? `${stored}/api/v1` : "http://localhost:3000/api/v1"; + if (stored) { + return `${stored}/api/v1`; + } + // 3. Default fallback + return "http://localhost:3000/api/v1"; }; export const getBaseUrl = () => { + if (window.env?.API_URL) { + return window.env.API_URL; + } const stored = localStorage.getItem("k_notes_api_url"); return stored ? stored : "http://localhost:3000"; } diff --git a/notes-api/src/main.rs b/notes-api/src/main.rs index 1f2d5b2..90ec6cb 100644 --- a/notes-api/src/main.rs +++ b/notes-api/src/main.rs @@ -94,19 +94,25 @@ async fn main() -> anyhow::Result<()> { .allow_credentials(true); // Add allowed origins + let mut allowed_origins = Vec::new(); for origin in &config.cors_allowed_origins { + tracing::debug!("Allowing CORS origin: {}", origin); if let Ok(value) = origin.parse::() { - cors = cors.allow_origin(value); + allowed_origins.push(value); } else { tracing::warn!("Invalid CORS origin: {}", origin); } } + if !allowed_origins.is_empty() { + cors = cors.allow_origin(allowed_origins); + } + // Build the application let app = Router::new() .nest("/api/v1", routes::api_v1_router()) - .layer(cors) .layer(auth_layer) + .layer(cors) .layer(TraceLayer::new_for_http()) .with_state(state);