This commit is contained in:
simon.franken
2026-02-16 17:12:47 +01:00
parent a9228d19c8
commit fc06dac40e
11 changed files with 49 additions and 46 deletions

5
.env.docker Normal file
View File

@@ -0,0 +1,5 @@
APP_URL=
OIDC_ISSUER_URL=
OIDC_CLIENT_ID=
SESSION_SECRET=
API_URL=

View File

@@ -1,17 +0,0 @@
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/timetracker"
# OIDC Configuration
OIDC_ISSUER_URL="https://your-oidc-provider.com"
OIDC_CLIENT_ID="your-client-id"
OIDC_REDIRECT_URI="http://localhost:3000/auth/callback"
# Session
SESSION_SECRET="your-session-secret-min-32-characters"
# Server
PORT=3001
NODE_ENV=development
# Frontend URL (for CORS)
FRONTEND_URL="http://localhost:5173"

View File

@@ -86,36 +86,41 @@ SESSION_SECRET="your-secure-session-secret-min-32-chars"
# Server # Server
PORT=3001 PORT=3001
NODE_ENV=development NODE_ENV=development
FRONTEND_URL="http://localhost:5173" APP_URL="http://localhost:5173"
``` ```
## API Endpoints ## API Endpoints
### Authentication ### Authentication
- `GET /auth/login` - Initiate OIDC login - `GET /auth/login` - Initiate OIDC login
- `GET /auth/callback` - OIDC callback - `GET /auth/callback` - OIDC callback
- `POST /auth/logout` - End session - `POST /auth/logout` - End session
- `GET /auth/me` - Get current user - `GET /auth/me` - Get current user
### Clients ### Clients
- `GET /api/clients` - List clients - `GET /api/clients` - List clients
- `POST /api/clients` - Create client - `POST /api/clients` - Create client
- `PUT /api/clients/:id` - Update client - `PUT /api/clients/:id` - Update client
- `DELETE /api/clients/:id` - Delete client - `DELETE /api/clients/:id` - Delete client
### Projects ### Projects
- `GET /api/projects` - List projects - `GET /api/projects` - List projects
- `POST /api/projects` - Create project - `POST /api/projects` - Create project
- `PUT /api/projects/:id` - Update project - `PUT /api/projects/:id` - Update project
- `DELETE /api/projects/:id` - Delete project - `DELETE /api/projects/:id` - Delete project
### Time Entries ### Time Entries
- `GET /api/time-entries` - List entries (with filters/pagination) - `GET /api/time-entries` - List entries (with filters/pagination)
- `POST /api/time-entries` - Create entry - `POST /api/time-entries` - Create entry
- `PUT /api/time-entries/:id` - Update entry - `PUT /api/time-entries/:id` - Update entry
- `DELETE /api/time-entries/:id` - Delete entry - `DELETE /api/time-entries/:id` - Delete entry
### Timer ### Timer
- `GET /api/timer` - Get ongoing timer - `GET /api/timer` - Get ongoing timer
- `POST /api/timer/start` - Start timer - `POST /api/timer/start` - Start timer
- `PUT /api/timer` - Update timer (set project) - `PUT /api/timer` - Update timer (set project)
@@ -134,6 +139,7 @@ User (oidc sub)
## Technology Stack ## Technology Stack
**Backend:** **Backend:**
- Node.js + Express - Node.js + Express
- TypeScript - TypeScript
- Prisma ORM - Prisma ORM
@@ -141,6 +147,7 @@ User (oidc sub)
- OpenID Client - OpenID Client
**Frontend:** **Frontend:**
- React 18 - React 18
- TypeScript - TypeScript
- TanStack Query - TanStack Query

View File

@@ -25,7 +25,7 @@ export const config = {
}, },
cors: { cors: {
origin: process.env.FRONTEND_URL || "http://localhost:5173", origin: process.env.APP_URL || "http://localhost:5173",
credentials: true, credentials: true,
}, },
}; };

View File

@@ -62,11 +62,11 @@ router.get("/callback", async (req, res) => {
delete req.session.oidc; delete req.session.oidc;
// Redirect to frontend // Redirect to frontend
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173"; const frontendUrl = process.env.APP_URL || "http://localhost:5173";
res.redirect(`${frontendUrl}/auth/callback?success=true`); res.redirect(`${frontendUrl}/auth/callback?success=true`);
} catch (error) { } catch (error) {
console.error("Callback error:", error); console.error("Callback error:", error);
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:5173"; const frontendUrl = process.env.APP_URL || "http://localhost:5173";
res.redirect(`${frontendUrl}/auth/callback?error=authentication_failed`); res.redirect(`${frontendUrl}/auth/callback?error=authentication_failed`);
} }
}); });

View File

@@ -1,5 +1,3 @@
version: '3.8'
services: services:
db: db:
image: postgres:16-alpine image: postgres:16-alpine
@@ -25,11 +23,11 @@ services:
DATABASE_URL: "postgresql://timetracker:timetracker_password@db:5432/timetracker" DATABASE_URL: "postgresql://timetracker:timetracker_password@db:5432/timetracker"
OIDC_ISSUER_URL: ${OIDC_ISSUER_URL} OIDC_ISSUER_URL: ${OIDC_ISSUER_URL}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
OIDC_REDIRECT_URI: "http://localhost:3001/api/auth/callback" OIDC_REDIRECT_URI: "${API_URL}/auth/callback"
SESSION_SECRET: ${SESSION_SECRET} SESSION_SECRET: ${SESSION_SECRET}
PORT: 3001 PORT: 3001
NODE_ENV: production NODE_ENV: production
FRONTEND_URL: "http://localhost:5173" APP_URL: "${APP_URL}"
ports: ports:
- "3001:3001" - "3001:3001"
depends_on: depends_on:
@@ -40,8 +38,8 @@ services:
build: build:
context: ./frontend context: ./frontend
dockerfile: Dockerfile dockerfile: Dockerfile
environment: args:
VITE_API_URL: "http://localhost:3001" - VITE_API_URL=${API_URL}
ports: ports:
- "5173:80" - "5173:80"
depends_on: depends_on:

View File

@@ -1,6 +1,9 @@
# Build stage # Build stage
FROM node:20-alpine as builder FROM node:20-alpine as builder
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import type { User } from "@/types"; import type { User } from "@/types";
const AUTH_BASE = "/api/auth"; const AUTH_BASE = import.meta.env.VITE_API_URL + "/auth";
export const authApi = { export const authApi = {
login: (): void => { login: (): void => {

View File

@@ -1,7 +1,7 @@
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: "/api", baseURL: import.meta.env.VITE_API_URL,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },

13
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
interface ViteTypeOptions {
// By adding this line, you can make the type of ImportMetaEnv strict
// to disallow unknown keys.
// strictImportMetaEnv: unknown
}
interface ImportMetaEnv {
VITE_API_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -2,22 +2,16 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import path from "path"; import path from "path";
const backend = "http://127.0.0.1:3001"; export default defineConfig(() => {
return {
export default defineConfig({ plugins: [react()],
plugins: [react()], resolve: {
resolve: { alias: {
alias: { "@": path.resolve(__dirname, "./src"),
"@": path.resolve(__dirname, "./src"),
},
},
server: {
port: 5173,
proxy: {
"/api": {
target: backend,
changeOrigin: true,
}, },
}, },
}, server: {
port: 5173,
},
};
}); });