fix: comprehensive quality improvements
- Fix 2 backend test failures (projectFolders validation, StressTest timeout) - Fix frontend test dependency (@testing-library/dom) - Delete App.tsx.bak from repo - Add rate limiting on auth endpoints (login: 10/min, register: 3/min) - Add .env.example with all environment variables - Update BAUPLAN.md with accurate phase status (16/21 phases implemented) - Add OpenAPI/Swagger documentation (docs/openapi.yaml) - Add E2E workflow test (15 steps: register→project→drawing→layer→element→CRUD→delete) - Add Forgejo CI/CD pipeline (.forgejo/workflows/ci.yml) - Improve .gitignore (db-wal, .env.*, *.bak, coverage) - Add security comment for default user in schema.sql All 628 tests passing (254 backend + 374 frontend)
This commit is contained in:
@@ -0,0 +1,799 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Web CAD API
|
||||
description: REST API for the Web-based 2D CAD for Event Seating Plans
|
||||
version: 1.0.0
|
||||
license:
|
||||
name: MIT
|
||||
servers:
|
||||
- url: http://localhost:3001
|
||||
description: Local development
|
||||
- url: https://web-cad-neu.server.media-on.de
|
||||
description: Production
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
schemas:
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
name:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [admin, planer, betrachter, gast]
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
Project:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
owner_id:
|
||||
type: string
|
||||
folder_id:
|
||||
type: string
|
||||
nullable: true
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
Drawing:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
project_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
data_json:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
Layer:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
drawing_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
visible:
|
||||
type: integer
|
||||
locked:
|
||||
type: integer
|
||||
color:
|
||||
type: string
|
||||
line_type:
|
||||
type: string
|
||||
transparency:
|
||||
type: number
|
||||
sort_order:
|
||||
type: integer
|
||||
parent_id:
|
||||
type: string
|
||||
nullable: true
|
||||
Element:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
drawing_id:
|
||||
type: string
|
||||
layer_id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [line, circle, arc, rect, polygon, polyline, text, dimension, block_instance, chair]
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
width:
|
||||
type: number
|
||||
height:
|
||||
type: number
|
||||
properties_json:
|
||||
type: string
|
||||
Block:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
drawing_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
category:
|
||||
type: string
|
||||
elements_json:
|
||||
type: string
|
||||
thumbnail:
|
||||
type: string
|
||||
nullable: true
|
||||
Session:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
userId:
|
||||
type: string
|
||||
expiresAt:
|
||||
type: number
|
||||
AuthResult:
|
||||
type: object
|
||||
properties:
|
||||
user:
|
||||
$ref: '#/components/schemas/User'
|
||||
session:
|
||||
$ref: '#/components/schemas/Session'
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
paths:
|
||||
/api/health:
|
||||
get:
|
||||
summary: Health check
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
/api/auth/register:
|
||||
post:
|
||||
summary: Register a new user
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [admin, planer, betrachter, gast]
|
||||
responses:
|
||||
'201':
|
||||
description: User created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthResult'
|
||||
'400':
|
||||
description: Missing fields
|
||||
'409':
|
||||
description: Email already registered
|
||||
'429':
|
||||
description: Rate limit exceeded
|
||||
|
||||
/api/auth/login:
|
||||
post:
|
||||
summary: Login
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Login successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AuthResult'
|
||||
'401':
|
||||
description: Invalid credentials
|
||||
'429':
|
||||
description: Rate limit exceeded
|
||||
|
||||
/api/auth/logout:
|
||||
post:
|
||||
summary: Logout
|
||||
responses:
|
||||
'204':
|
||||
description: Logged out
|
||||
|
||||
/api/auth/me:
|
||||
get:
|
||||
summary: Get current user profile
|
||||
responses:
|
||||
'200':
|
||||
description: Current user
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'401':
|
||||
description: Not authenticated
|
||||
|
||||
/api/auth/password:
|
||||
patch:
|
||||
summary: Change password
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
oldPassword:
|
||||
type: string
|
||||
newPassword:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Password changed
|
||||
'400':
|
||||
description: Invalid old password
|
||||
|
||||
/api/projects:
|
||||
get:
|
||||
summary: List projects
|
||||
parameters:
|
||||
- name: folderId
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by folder ID (null for root)
|
||||
responses:
|
||||
'200':
|
||||
description: List of projects
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Project'
|
||||
post:
|
||||
summary: Create a project
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
folder_id:
|
||||
type: string
|
||||
nullable: true
|
||||
responses:
|
||||
'201':
|
||||
description: Project created
|
||||
|
||||
/api/projects/{id}:
|
||||
get:
|
||||
summary: Get a project
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Project details
|
||||
'404':
|
||||
description: Not found
|
||||
put:
|
||||
summary: Update a project
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Updated
|
||||
delete:
|
||||
summary: Delete a project
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/projects/{id}/folder:
|
||||
put:
|
||||
summary: Move project to folder
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
folder_id:
|
||||
type: string
|
||||
nullable: true
|
||||
responses:
|
||||
'200':
|
||||
description: Moved
|
||||
|
||||
/api/project-folders:
|
||||
get:
|
||||
summary: List project folders
|
||||
responses:
|
||||
'200':
|
||||
description: List of folders
|
||||
post:
|
||||
summary: Create a folder
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
parent_id:
|
||||
type: string
|
||||
nullable: true
|
||||
responses:
|
||||
'201':
|
||||
description: Folder created
|
||||
|
||||
/api/project-folders/{id}:
|
||||
get:
|
||||
summary: Get a folder
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Folder details
|
||||
put:
|
||||
summary: Rename a folder
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Renamed
|
||||
delete:
|
||||
summary: Delete a folder
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/drawings:
|
||||
get:
|
||||
summary: List drawings for a project
|
||||
parameters:
|
||||
- name: projectId
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of drawings
|
||||
post:
|
||||
summary: Create a drawing
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
project_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: Drawing created
|
||||
|
||||
/api/drawings/{id}:
|
||||
get:
|
||||
summary: Get a drawing
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Drawing details
|
||||
put:
|
||||
summary: Update a drawing
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Updated
|
||||
delete:
|
||||
summary: Delete a drawing
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/drawings/{drawingId}/layers:
|
||||
get:
|
||||
summary: List layers for a drawing
|
||||
parameters:
|
||||
- name: drawingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of layers
|
||||
post:
|
||||
summary: Create a layer
|
||||
parameters:
|
||||
- name: drawingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: Layer created
|
||||
|
||||
/api/layers/{id}:
|
||||
patch:
|
||||
summary: Update a layer
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Updated
|
||||
delete:
|
||||
summary: Delete a layer
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/drawings/{drawingId}/elements:
|
||||
get:
|
||||
summary: List elements for a drawing
|
||||
parameters:
|
||||
- name: drawingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of elements
|
||||
post:
|
||||
summary: Create an element
|
||||
parameters:
|
||||
- name: drawingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: Element created
|
||||
|
||||
/api/elements/{id}:
|
||||
put:
|
||||
summary: Update an element
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Updated
|
||||
delete:
|
||||
summary: Delete an element
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/drawings/{drawingId}/blocks:
|
||||
get:
|
||||
summary: List blocks for a drawing
|
||||
parameters:
|
||||
- name: drawingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of blocks
|
||||
post:
|
||||
summary: Create a block
|
||||
parameters:
|
||||
- name: drawingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: Block created
|
||||
|
||||
/api/blocks/{id}:
|
||||
put:
|
||||
summary: Update a block
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Updated
|
||||
delete:
|
||||
summary: Delete a block
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/settings:
|
||||
get:
|
||||
summary: Get all settings
|
||||
responses:
|
||||
'200':
|
||||
description: Settings object
|
||||
put:
|
||||
summary: Set a setting value
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Setting saved
|
||||
|
||||
/api/users:
|
||||
get:
|
||||
summary: List users (admin only)
|
||||
responses:
|
||||
'200':
|
||||
description: List of users
|
||||
|
||||
/api/users/{id}:
|
||||
put:
|
||||
summary: Update a user (admin only)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Updated
|
||||
delete:
|
||||
summary: Delete a user (admin only)
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Deleted
|
||||
|
||||
/api/ai/chat:
|
||||
post:
|
||||
summary: KI Copilot chat proxy
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
messages:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
role:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: AI response
|
||||
'401':
|
||||
description: Not authenticated
|
||||
|
||||
/api/notifications:
|
||||
get:
|
||||
summary: List notifications
|
||||
responses:
|
||||
'200':
|
||||
description: List of notifications
|
||||
post:
|
||||
summary: Create a notification
|
||||
responses:
|
||||
'201':
|
||||
description: Created
|
||||
|
||||
/api/notifications/{id}/read:
|
||||
patch:
|
||||
summary: Mark notification as read
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Marked as read
|
||||
|
||||
/api/projects/{id}/shares:
|
||||
get:
|
||||
summary: List shares for a project
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: List of shares
|
||||
post:
|
||||
summary: Share a project
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: Shared
|
||||
|
||||
/api/global-blocks:
|
||||
get:
|
||||
summary: List global blocks
|
||||
responses:
|
||||
'200':
|
||||
description: List of global blocks
|
||||
post:
|
||||
summary: Create a global block
|
||||
responses:
|
||||
'201':
|
||||
description: Created
|
||||
|
||||
/api/global-block-folders:
|
||||
get:
|
||||
summary: List global block folders
|
||||
responses:
|
||||
'200':
|
||||
description: List of folders
|
||||
post:
|
||||
summary: Create a global block folder
|
||||
responses:
|
||||
'201':
|
||||
description: Created
|
||||
Reference in New Issue
Block a user