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

View File

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

View File

@@ -62,11 +62,11 @@ router.get("/callback", async (req, res) => {
delete req.session.oidc;
// 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`);
} catch (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`);
}
});

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import axios, { AxiosError } from "axios";
const apiClient = axios.create({
baseURL: "/api",
baseURL: import.meta.env.VITE_API_URL,
headers: {
"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 path from "path";
const backend = "http://127.0.0.1:3001";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
port: 5173,
proxy: {
"/api": {
target: backend,
changeOrigin: true,
export default defineConfig(() => {
return {
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
},
server: {
port: 5173,
},
};
});