1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 08:29:36 +01:00

refactor: restructure repository with separate backend and frontend directories

- Move Java backend to backend/ directory
- Create frontend/ directory for TypeScript TUI and future WebUI
- Update .gitignore for Node.js and worktrees
- Update README.md with new repository structure
- Copy documentation to backend/
This commit is contained in:
Sebastian Frick 2026-02-17 22:08:51 +01:00
parent ec9114aa0a
commit c2c48a03e8
141 changed files with 734 additions and 9 deletions

13
.gitignore vendored
View file

@ -50,3 +50,16 @@ coverage/
.mvn/ .mvn/
mvnw mvnw
mvnw.cmd mvnw.cmd
# Node.js / Frontend
frontend/node_modules/
frontend/dist/
frontend/build/
frontend/.pnpm-store/
frontend/**/.turbo/
frontend/**/.next/
*.tsbuildinfo
.pnpm-debug.log
# Git worktrees
.worktrees/

View file

@ -2,6 +2,20 @@
ERP-System für Fleischereien mit HACCP-Compliance, GoBD-konform, Mehrfilialen-Support. ERP-System für Fleischereien mit HACCP-Compliance, GoBD-konform, Mehrfilialen-Support.
## Repository Structure
```
effigenix/
├── backend/ # Java Spring Boot Backend
│ ├── src/ # Java source code (DDD + Clean Architecture)
│ ├── docs/ # Backend documentation
│ └── pom.xml # Maven configuration
└── frontend/ # TypeScript Frontend (TUI & WebUI)
├── apps/ # Applications (CLI TUI, WebUI)
└── packages/ # Shared packages (api-client, types, validation)
```
## Architektur ## Architektur
**Domain-Driven Design + Clean Architecture + Java 21 + Spring Boot** **Domain-Driven Design + Clean Architecture + Java 21 + Spring Boot**
@ -58,14 +72,15 @@ ERP-System für Fleischereien mit HACCP-Compliance, GoBD-konform, Mehrfilialen-S
## Getting Started ## Getting Started
### Prerequisites ### Backend (Java Spring Boot)
**Prerequisites:**
- Java 21+ - Java 21+
- Maven 3.9+ - Maven 3.9+
- PostgreSQL 15+ - PostgreSQL 15+
- Docker (optional, für PostgreSQL) - Docker (optional, für PostgreSQL)
### Database Setup **Database Setup:**
```bash ```bash
# PostgreSQL mit Docker # PostgreSQL mit Docker
@ -77,9 +92,11 @@ docker run --name effigenix-postgres \
-d postgres:15 -d postgres:15
``` ```
### Build & Run **Build & Run:**
```bash ```bash
cd backend
# Build # Build
mvn clean install mvn clean install
@ -90,16 +107,41 @@ mvn spring-boot:run
mvn spring-boot:run -Dspring-boot.run.profiles=dev mvn spring-boot:run -Dspring-boot.run.profiles=dev
``` ```
### API Documentation **API Documentation:**
Nach dem Start verfügbar unter: Nach dem Start verfügbar unter:
- Swagger UI: http://localhost:8080/swagger-ui.html - Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI Spec: http://localhost:8080/api-docs - OpenAPI Spec: http://localhost:8080/api-docs
### Frontend (TypeScript TUI)
**Prerequisites:**
- Node.js 20+
- pnpm 9+
**Setup & Run:**
```bash
cd frontend
# Install dependencies
pnpm install
# Run TUI in dev mode
pnpm run dev
# Build for production
pnpm run build
```
Detailed frontend documentation: [frontend/README.md](frontend/README.md)
## Project Structure ## Project Structure
**Backend (Java):**
``` ```
src/main/java/com/effigenix/ backend/src/main/java/de/effigenix/
├── domain/ # Domain Layer (keine Framework-Dependencies!) ├── domain/ # Domain Layer (keine Framework-Dependencies!)
│ └── usermanagement/ │ └── usermanagement/
│ ├── User.java │ ├── User.java
@ -120,8 +162,27 @@ src/main/java/com/effigenix/
├── security/ # AuthorizationPort, Action ├── security/ # AuthorizationPort, Action
└── common/ # Result, ApplicationError └── common/ # Result, ApplicationError
src/main/resources/ backend/src/main/resources/
└── db/migration/ # Flyway Migrations └── db/migration/ # Liquibase Migrations
```
**Frontend (TypeScript):**
```
frontend/
├── apps/
│ └── cli/ # Terminal UI (Ink)
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── hooks/ # React hooks
│ │ └── state/ # State management
│ └── package.json
└── packages/ # Shared packages (reusable for WebUI)
├── api-client/ # HTTP client für Backend API
├── types/ # TypeScript types (generated from OpenAPI)
├── validation/ # Zod schemas
└── config/ # Shared configuration
``` ```
## User Management (Generic Subdomain) ## User Management (Generic Subdomain)
@ -166,7 +227,11 @@ public class CreateRecipe {
## Testing ## Testing
**Backend:**
```bash ```bash
cd backend
# Unit Tests # Unit Tests
mvn test mvn test
@ -177,6 +242,21 @@ mvn verify
mvn clean verify jacoco:report mvn clean verify jacoco:report
``` ```
**Frontend:**
```bash
cd frontend
# Unit Tests
pnpm test
# Test Coverage
pnpm run test:coverage
# Type Check
pnpm run typecheck
```
## License ## License
Proprietary - Effigenix GmbH Proprietary - Effigenix GmbH

View file

@ -7,7 +7,7 @@
3. ✅ Inventory BC implementieren (Basis: 8.1-8.3) 3. ✅ Inventory BC implementieren (Basis: 8.1-8.3)
4. ✅ Document Archive BC (Basis: 12.1-12.2) - parallel zu Inventory 4. ✅ Document Archive BC (Basis: 12.1-12.2) - parallel zu Inventory
- [ ] Liquibase statt Flyway - [x] Liquibase statt Flyway
- [ ] Package Struktur gemäß DDD-model skill, ddd-implementer fragen wegen refactor? - [x] Package Struktur gemäß DDD-model skill, ddd-implementer fragen wegen refactor?
- [ ] ActionToPermissionMapper, warum unterschiedliches Vorgehen if/else vs. switch/case - [ ] ActionToPermissionMapper, warum unterschiedliches Vorgehen if/else vs. switch/case
- [ ] Nix Shell für manuelles Testing mit Postgres sowie für Migrationstests - [ ] Nix Shell für manuelles Testing mit Postgres sowie für Migrationstests

92
backend/.factorypath Normal file
View file

@ -0,0 +1,92 @@
<factorypath>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-web/3.2.2/spring-boot-starter-web-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter/3.2.2/spring-boot-starter-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot/3.2.2/spring-boot-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/3.2.2/spring-boot-autoconfigure-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-logging/3.2.2/spring-boot-starter-logging-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-classic/1.4.14/logback-classic-1.4.14.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-core/1.4.14/logback-core-1.4.14.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.21.1/log4j-to-slf4j-2.21.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-api/2.21.1/log4j-api-2.21.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/jul-to-slf4j/2.0.11/jul-to-slf4j-2.0.11.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-json/3.2.2/spring-boot-starter-json-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.15.3/jackson-datatype-jdk8-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.15.3/jackson-datatype-jsr310-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/module/jackson-module-parameter-names/2.15.3/jackson-module-parameter-names-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-tomcat/3.2.2/spring-boot-starter-tomcat-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.18/tomcat-embed-websocket-10.1.18.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-web/6.1.3/spring-web-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-beans/6.1.3/spring-beans-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/micrometer/micrometer-observation/1.12.2/micrometer-observation-1.12.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/micrometer/micrometer-commons/1.12.2/micrometer-commons-1.12.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-webmvc/6.1.3/spring-webmvc-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-context/6.1.3/spring-context-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-expression/6.1.3/spring-expression-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-data-jpa/3.2.2/spring-boot-starter-data-jpa-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-aop/3.2.2/spring-boot-starter-aop-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/aspectj/aspectjweaver/1.9.21/aspectjweaver-1.9.21.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-jdbc/3.2.2/spring-boot-starter-jdbc-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/zaxxer/HikariCP/5.0.1/HikariCP-5.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jdbc/6.1.3/spring-jdbc-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/orm/hibernate-core/6.4.1.Final/hibernate-core-6.4.1.Final.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/persistence/jakarta.persistence-api/3.1.0/jakarta.persistence-api-3.1.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/transaction/jakarta.transaction-api/2.0.1/jakarta.transaction-api-2.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/logging/jboss-logging/3.5.3.Final/jboss-logging-3.5.3.Final.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/common/hibernate-commons-annotations/6.0.6.Final/hibernate-commons-annotations-6.0.6.Final.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/smallrye/jandex/3.1.2/jandex-3.1.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/classmate/1.6.0/classmate-1.6.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/net/bytebuddy/byte-buddy/1.14.11/byte-buddy-1.14.11.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/jaxb-runtime/4.0.4/jaxb-runtime-4.0.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/jaxb-core/4.0.4/jaxb-core-4.0.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/eclipse/angus/angus-activation/2.0.1/angus-activation-2.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/txw2/4.0.4/txw2-4.0.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/sun/istack/istack-commons-runtime/4.1.2/istack-commons-runtime-4.1.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/antlr/antlr4-runtime/4.13.0/antlr4-runtime-4.13.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-jpa/3.2.2/spring-data-jpa-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-commons/3.2.2/spring-data-commons-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-orm/6.1.3/spring-orm-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-tx/6.1.3/spring-tx-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/slf4j-api/2.0.11/slf4j-api-2.0.11.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aspects/6.1.3/spring-aspects-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-validation/3.2.2/spring-boot-starter-validation-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/10.1.18/tomcat-embed-el-10.1.18.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/validator/hibernate-validator/8.0.1.Final/hibernate-validator-8.0.1.Final.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/validation/jakarta.validation-api/3.0.2/jakarta.validation-api-3.0.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-security/3.2.2/spring-boot-starter-security-3.2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aop/6.1.3/spring-aop-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-config/6.2.1/spring-security-config-6.2.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-web/6.2.1/spring-security-web-6.2.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt-api/0.12.5/jjwt-api-0.12.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt-impl/0.12.5/jjwt-impl-0.12.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt-jackson/0.12.5/jjwt-jackson-0.12.5.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.15.3/jackson-databind-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-annotations/2.15.3/jackson-annotations-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.15.3/jackson-core-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/postgresql/postgresql/42.6.0/postgresql-42.6.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/checkerframework/checker-qual/3.31.0/checker-qual-3.31.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/liquibase/liquibase-core/4.24.0/liquibase-core-4.24.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/opencsv/opencsv/5.8/opencsv-5.8.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-lang3/3.13.0/commons-lang3-3.13.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-collections4/4.4/commons-collections4-4.4.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springdoc/springdoc-openapi-starter-webmvc-ui/2.3.0/springdoc-openapi-starter-webmvc-ui-2.3.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springdoc/springdoc-openapi-starter-webmvc-api/2.3.0/springdoc-openapi-starter-webmvc-api-2.3.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springdoc/springdoc-openapi-starter-common/2.3.0/springdoc-openapi-starter-common-2.3.0.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/swagger/core/v3/swagger-core-jakarta/2.2.19/swagger-core-jakarta-2.2.19.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/swagger/core/v3/swagger-annotations-jakarta/2.2.19/swagger-annotations-jakarta-2.2.19.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/io/swagger/core/v3/swagger-models-jakarta/2.2.19/swagger-models-jakarta-2.2.19.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.15.3/jackson-dataformat-yaml-2.15.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/webjars/swagger-ui/5.10.3/swagger-ui-5.10.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/xml/bind/jakarta.xml.bind-api/4.0.1/jakarta.xml.bind-api-4.0.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/jakarta/activation/jakarta.activation-api/2.1.2/jakarta.activation-api-2.1.2.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-core/6.1.3/spring-core-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jcl/6.1.3/spring-jcl-6.1.3.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-core/6.2.1/spring-security-core-6.2.1.jar" enabled="true" runInBatchMode="false"/>
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-crypto/6.2.1/spring-security-crypto-6.2.1.jar" enabled="true" runInBatchMode="false"/>
</factorypath>

47
backend/CLAUDE.md Normal file
View file

@ -0,0 +1,47 @@
# Effigenix ERP Agent Guide
## Stack
Java 21, Spring Boot 3.2, PostgreSQL, Liquibase, JWT (JJWT), Maven
## Architektur
DDD + Clean Architecture. Einweg-Abhängigkeit: `domain → application → infrastructure`.
```
de.effigenix.
├── domain.{bc}/ # Reine Geschäftslogik, KEINE Framework-Deps
├── application.{bc}/ # Use Cases, Commands, DTOs
├── infrastructure.{bc}/ # JPA, REST, Security, Audit
└── shared/ # Shared Kernel (Result<E,T>, AuthorizationPort, Action)
```
Bounded Contexts: `usermanagement` (implementiert), `production`, `quality`, `inventory`, `procurement`, `sales`, `labeling`, `filiales` (Platzhalter).
## Namenskonventionen
| Artefakt | Muster | Beispiel |
|---|---|---|
| Use Case | `{Verb}{Noun}` | `CreateUser`, `AuthenticateUser` |
| Command | `{Verb}{Noun}Command` | `CreateUserCommand` |
| Domain Entity | `{Noun}` | `User`, `Role` |
| Value Object | `{Noun}` | `UserId`, `PasswordHash`, `RoleName` |
| Domain Error | `{Noun}Error` (sealed interface) | `UserError.UsernameAlreadyExists` |
| JPA Entity | `{Noun}Entity` | `UserEntity` |
| Mapper | `{Noun}Mapper` | `UserMapper` (Domain↔JPA) |
| Repository (Domain) | `{Noun}Repository` | `UserRepository` (Interface) |
| Repository (Impl) | `Jpa{Noun}Repository` | `JpaUserRepository` |
| Controller | `{Noun}Controller` | `UserController` |
| Web DTO | `{Verb}{Noun}Request` | `CreateUserRequest` |
| Action Enum | `{Noun}Action implements Action` | `ProductionAction` |
## Error Handling
Funktional via `Result<E, T>` (`shared.common.Result`). Domain-Fehler sind sealed interfaces mit Records. Keine Exceptions im Domain/Application Layer.
## Commits
Conventional Commits. Kein `Co-Authored-By` Header niemals.
## DDD Skill
Für neue Bounded Contexts: `/ddd-implement` Skill verwenden. Dokumentation unter `.claude/skills/ddd-implement/SKILL.md`.
## Doku
- `docs/QUICK_START.md` Lokale Entwicklung, Docker, Seed-Daten
- `docs/USER_MANAGEMENT.md` Referenz-BC mit AuthorizationPort, JWT, Audit
- `TODO.md` Offene Aufgaben und Waves

188
backend/README.md Normal file
View file

@ -0,0 +1,188 @@
# Effigenix Fleischerei ERP
ERP-System für Fleischereien mit HACCP-Compliance, GoBD-konform, Mehrfilialen-Support.
## Architektur
**Domain-Driven Design + Clean Architecture + Java 21 + Spring Boot**
```
┌─────────────────────────────────────────────────────┐
│ Presentation (REST Controllers) │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Application Layer (Use Cases) │
│ - Transaction Script for Generic Subdomains │
│ - Rich Domain Model for Core Domains │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Domain Layer (DDD Tactical Patterns) │
│ - Aggregates, Entities, Value Objects │
│ - Domain Events, Repositories │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ - Spring, JPA, PostgreSQL, JWT, REST │
└─────────────────────────────────────────────────────┘
```
## Bounded Contexts (11)
### Core Domains (7)
- **Production Management** - Rezeptverwaltung, Chargenproduktion
- **Quality Management** - HACCP-Compliance, Temperaturüberwachung
- **Inventory Management** - Bestandsführung, Lagerverwaltung
- **Procurement** - Einkauf, Wareneingang, Lieferanten
- **Sales** - Auftragserfassung, Rechnungsstellung, Kunden
### Supporting Domains (3)
- **Labeling** - Etikettendruck mit HACCP-Daten
- **Filiales** - Mehrfilialen-Verwaltung
### Generic Subdomains (3)
- **User Management** - Authentifizierung, Autorisierung, Rollen
- **Reporting** - Standard-Reports
- **Notifications** - E-Mail/SMS-Benachrichtigungen
## Tech Stack
- **Java 21** (Records, Sealed Interfaces, Pattern Matching)
- **Spring Boot 3.2** (Spring Security 6, Spring Data JPA 3)
- **PostgreSQL 15+** (Produktiv-DB)
- **JWT** (Stateless Authentication)
- **Flyway** (Schema Migrations)
- **Maven** (Build Tool)
## Getting Started
### Prerequisites
- Java 21+
- Maven 3.9+
- PostgreSQL 15+
- Docker (optional, für PostgreSQL)
### Database Setup
```bash
# PostgreSQL mit Docker
docker run --name effigenix-postgres \
-e POSTGRES_DB=effigenix \
-e POSTGRES_USER=effigenix \
-e POSTGRES_PASSWORD=effigenix \
-p 5432:5432 \
-d postgres:15
```
### Build & Run
```bash
# Build
mvn clean install
# Run
mvn spring-boot:run
# Run with specific profile
mvn spring-boot:run -Dspring-boot.run.profiles=dev
```
### API Documentation
Nach dem Start verfügbar unter:
- Swagger UI: http://localhost:8080/swagger-ui.html
- OpenAPI Spec: http://localhost:8080/api-docs
## Project Structure
```
src/main/java/com/effigenix/
├── domain/ # Domain Layer (keine Framework-Dependencies!)
│ └── usermanagement/
│ ├── User.java
│ ├── Role.java
│ ├── UserId.java
│ └── UserRepository.java
├── application/ # Application Layer (Use Cases)
│ └── usermanagement/
│ ├── CreateUser.java
│ ├── AuthenticateUser.java
│ └── dto/
├── infrastructure/ # Infrastructure Layer
│ ├── persistence/
│ ├── security/
│ ├── web/
│ └── audit/
└── shared/ # Shared Kernel
├── security/ # AuthorizationPort, Action
└── common/ # Result, ApplicationError
src/main/resources/
└── db/migration/ # Flyway Migrations
```
## User Management (Generic Subdomain)
### Vordefinierte Rollen
| Rolle | Permissions | Zielgruppe |
|-------|-------------|------------|
| **ADMIN** | Alle | Systemadministrator |
| **PRODUCTION_MANAGER** | RECIPE_*, BATCH_*, PRODUCTION_ORDER_* | Leiter Produktion |
| **PRODUCTION_WORKER** | RECIPE_READ, BATCH_* | Produktionsmitarbeiter |
| **QUALITY_MANAGER** | HACCP_*, TEMPERATURE_LOG_* | Qualitätsbeauftragter |
| **QUALITY_INSPECTOR** | TEMPERATURE_LOG_*, GOODS_INSPECTION_* | QM-Mitarbeiter |
| **PROCUREMENT_MANAGER** | PURCHASE_ORDER_*, SUPPLIER_* | Einkaufsleiter |
| **WAREHOUSE_WORKER** | STOCK_*, INVENTORY_COUNT_* | Lagermitarbeiter |
| **SALES_MANAGER** | ORDER_*, INVOICE_*, CUSTOMER_* | Verkaufsleiter |
| **SALES_STAFF** | ORDER_READ/WRITE, CUSTOMER_READ | Verkaufsmitarbeiter |
### AuthorizationPort (für andere BCs)
```java
// Typsichere, fachliche Authorization - kein direkter Zugriff auf User/Roles!
public interface AuthorizationPort {
boolean can(Action action);
void assertCan(Action action);
boolean can(Action action, ResourceId resource);
void assertCan(Action action, ResourceId resource);
ActorId currentActor();
Optional<BranchId> currentBranch();
}
// Beispiel: Production BC
public class CreateRecipe {
private final AuthorizationPort authPort;
public Result<ApplicationError, RecipeDTO> execute(CreateRecipeCommand cmd) {
authPort.assertCan(ProductionAction.RECIPE_WRITE);
// Business logic...
}
}
```
## Testing
```bash
# Unit Tests
mvn test
# Integration Tests
mvn verify
# Test Coverage
mvn clean verify jacoco:report
```
## License
Proprietary - Effigenix GmbH
## Contact
- Project Lead: sebi@effigenix.com
- Architecture: DDD + Clean Architecture
- Documentation: /docs/mvp/

13
backend/TODO.md Normal file
View file

@ -0,0 +1,13 @@
Welle 1 (sofort starten):
1. ✅ User Management BC implementieren
2. ✅ Master Data BC implementieren (Artikel, Lieferanten, Kunden)
Welle 2 (parallel):
3. ✅ Inventory BC implementieren (Basis: 8.1-8.3)
4. ✅ Document Archive BC (Basis: 12.1-12.2) - parallel zu Inventory
- [x] Liquibase statt Flyway
- [x] Package Struktur gemäß DDD-model skill, ddd-implementer fragen wegen refactor?
- [ ] ActionToPermissionMapper, warum unterschiedliches Vorgehen if/else vs. switch/case
- [ ] Nix Shell für manuelles Testing mit Postgres sowie für Migrationstests

View file

@ -0,0 +1,292 @@
# Ticket 001 Code Review: User Management Bounded Context
**Datum:** 2026-02-17
**Commit:** `ec9114a` feat: add Spring Boot ERP application with user management domain
**Status:** Offen
---
## Kritisch
### K1 Aggregates sind nicht immutable
**Betroffene Dateien:**
- `domain/usermanagement/User.java` (Zeilen 2229)
- `domain/usermanagement/Role.java` (Zeilen 1821)
**Problem:**
Felder in `User` und `Role` sind nicht `final`. Business-Methoden wie `lock()`, `unlock()`, `updateEmail()`, `changePassword()` mutieren den internen State direkt statt neue Instanzen oder `Result` zurückzugeben. Die interne `roles`-Collection in `User` (Zeile 25) ist mutable und wird per `assignRole()`/`removeRole()` in-place modifiziert.
**Lösung:**
- Alle Felder `final` machen
- Business-Methoden geben `Result<UserError, User>` zurück (Copy-on-Write)
- Interne Collections nur über defensive Kopien exponieren
---
### K2 Fehlende Command-Validierung im Application Layer
**Betroffene Dateien:**
- `application/usermanagement/command/CreateUserCommand.java`
- `application/usermanagement/command/UpdateUserCommand.java`
- `application/usermanagement/command/AuthenticateCommand.java`
- `application/usermanagement/command/ChangePasswordCommand.java`
- `application/usermanagement/command/AssignRoleCommand.java`
**Problem:**
Alle Commands sind reine Records ohne jegliche Validierung. Wird der Application Layer außerhalb des HTTP-Kontexts aufgerufen (z.B. Scheduled Jobs, Message Queues), gibt es keine Input-Checks. Die Validierung liegt ausschließlich in den Web-DTOs (`@Valid`-Annotationen).
**Lösung:**
- Self-validating Commands mit Factory-Methoden, die `Result` zurückgeben
- Oder Validierung als ersten Schritt im Use Case
---
### K3 Authorization fehlt im Application Layer
**Betroffene Dateien:**
- `application/usermanagement/CreateUser.java`
- `application/usermanagement/LockUser.java`
- `application/usermanagement/AssignRole.java`
- (alle Use Cases betroffen)
**Problem:**
`AuthorizationPort` wird im Application Layer nicht verwendet. Autorisierung liegt ausschließlich auf `@PreAuthorize` im Controller. Bei Non-HTTP-Aufrufen wird die Authorization komplett umgangen.
**Lösung:**
- `AuthorizationPort.can(actorId, action)` als ersten Check in jedem Use Case aufrufen
- Bei fehlender Berechtigung `Result.failure(UserError.Unauthorized(...))` zurückgeben
---
### K4 Token-Blacklist Memory Leak
**Betroffene Datei:**
- `infrastructure/security/JwtSessionManager.java` (Zeilen 3536, 164168)
**Problem:**
Die Token-Blacklist nutzt `ConcurrentHashMap.newKeySet()` ohne TTL oder Cleanup. Bei laufendem Server wächst die Blacklist unbegrenzt. Nach Server-Restart ist die Blacklist leer ausgeloggte Tokens werden wieder gültig. Funktioniert nicht in Cluster-Deployments.
**Lösung:**
- Redis-basierte Blacklist mit TTL = Token-Expiration
- Oder In-Memory mit Scheduled Cleanup abgelaufener Tokens
---
### K5 Audit-Bug in ChangePassword
**Betroffene Datei:**
- `application/usermanagement/ChangePassword.java` (Zeile 52)
**Problem:**
Im Fehlerfall wird `AuditEvent.PASSWORD_CHANGED` geloggt statt `PASSWORD_CHANGE_FAILED`. Falsche Audit-Events verfälschen das Security-Monitoring.
**Lösung:**
- Korrektes Event `PASSWORD_CHANGE_FAILED` im Fehlerfall loggen
- Audit-Events auf Success/Failure-Pfade prüfen
---
## Mittel
### M1 Fehlende Status-Transitionen-Validierung
**Betroffene Datei:**
- `domain/usermanagement/User.java` (Zeilen 118132)
**Problem:**
`lock()`, `unlock()`, `deactivate()`, `activate()` validieren den aktuellen Status nicht. Invalide Übergänge sind möglich (z.B. einen inaktiven User sperren). Keine Idempotenz-Checks.
**Lösung:**
- State-Machine oder explizite Guards für erlaubte Übergänge
- `Result<UserError, User>` als Rückgabetyp
---
### M2 Inkonsistente Result-Nutzung in Domain-Methoden
**Betroffene Datei:**
- `domain/usermanagement/User.java` (Zeilen 106159)
**Problem:**
Manche Business-Methoden geben `Result` zurück (`assignRole()`, `changePassword()`), andere `void` (`removeRole()`, `lock()`, `unlock()`, `updateBranch()`, `updateLastLogin()`). Inkonsistentes API-Design.
**Lösung:**
- Alle Business-Methoden einheitlich auf `Result<UserError, User>` umstellen (siehe K1)
---
### M3 RemoveRole bricht Command-Pattern
**Betroffene Datei:**
- `application/usermanagement/RemoveRole.java` (Zeile 44)
**Problem:**
Einziger Use Case, der Raw-Parameter statt eines Command-Objekts akzeptiert:
```java
public Result<UserError, UserDTO> execute(String userId, RoleName roleName, ActorId performedBy)
```
Alle anderen Use Cases nutzen Command-Records.
**Lösung:**
- `RemoveRoleCommand` Record erstellen und in `execute()` verwenden
---
### M4 Refresh Token nicht implementiert
**Betroffene Datei:**
- `infrastructure/security/JwtSessionManager.java` (Zeilen 141144)
**Problem:**
`refreshSession()` wirft `UnsupportedOperationException`. User-Status (gesperrt/gelöscht) wird beim Token-Refresh nicht geprüft.
**Lösung:**
- Implementierung: User laden, Status verifizieren, neues Token-Paar ausstellen
- Altes Refresh-Token auf Blacklist setzen
---
### M5 Kein Rate Limiting auf Login-Endpoint
**Betroffene Datei:**
- `infrastructure/usermanagement/web/controller/AuthController.java` (Zeile 90)
**Problem:**
`/api/auth/login` hat keinen Brute-Force-Schutz. Angreifer können Credentials unbegrenzt durchprobieren.
**Lösung:**
- Rate Limiting per IP/Username (z.B. Bucket4j, Spring Cloud Gateway)
- Account-Lockout nach N fehlgeschlagenen Versuchen
- Exponentielles Backoff
---
### M6 CORS deaktiviert statt konfiguriert
**Betroffene Datei:**
- `infrastructure/security/SecurityConfig.java` (Zeile 69)
**Problem:**
CORS ist komplett deaktiviert (`AbstractHttpConfigurer::disable`). TODO-Kommentar im Code, aber keine Konfiguration. Browser-basierte Clients können keine Cross-Origin-Requests senden.
**Lösung:**
- CORS mit expliziten Allowed Origins, Methods und Headers konfigurieren
- Environment-abhängig (Dev: localhost, Prod: spezifische Domain)
---
### M7 Error Response Information Disclosure
**Betroffene Datei:**
- `infrastructure/security/SecurityConfig.java` (Zeilen 100113)
**Problem:**
Custom Exception Handler gibt `authException.getMessage()` direkt im JSON-Response zurück. Kann interne Implementierungsdetails exponieren.
**Lösung:**
- Generische Fehlermeldungen für Authentication/Authorization Failures
- Details nur ins Server-Log schreiben
---
## Niedrig
### N1 Swagger UI öffentlich zugänglich
**Betroffene Datei:**
- `infrastructure/security/SecurityConfig.java` (Zeile 83)
- `application.yml` (Zeile 56)
**Problem:**
Swagger UI ist standardmäßig ohne Authentifizierung erreichbar. Exponiert alle API-Endpoints mit Parametern.
**Lösung:**
- In Production deaktivieren oder hinter Authentication legen
- Spring-Profile nutzen: `springdoc.swagger-ui.enabled=${SWAGGER_ENABLED:false}`
---
### N2 Default-Admin Credentials in Seed-Daten
**Betroffene Datei:**
- `db/changelog/changes/004-seed-admin-user.sql` (Zeilen 23, 26)
**Problem:**
Admin-Credentials `admin/admin123` stehen als Kommentar in der SQL-Datei und als Table-Comment in der Datenbank. Bei versehentlicher DB-Exposure sind Credentials sofort sichtbar.
**Lösung:**
- Seed-Daten nur in Dev-Profil ausführen
- Table-Comment mit Credentials entfernen
- Production: Admin über Environment-Variablen oder Init-Script bootstrappen
---
### N3 CreateUser erlaubt User ohne Rollen
**Betroffene Datei:**
- `application/usermanagement/CreateUser.java` (Zeile 76)
**Problem:**
Wenn `cmd.roleNames()` leer ist, wird ein User ohne Rollen erstellt. Kein Check, ob mindestens eine Rolle zugewiesen wird.
**Lösung:**
- Validierung in Command oder Use Case: mindestens eine Rolle erforderlich
---
### N4 Falscher Error-Typ in Role.addPermission()
**Betroffene Datei:**
- `domain/usermanagement/Role.java` (Zeile 70)
**Problem:**
Bei `null`-Permission wird `UserError.NullRole()` verwendet semantisch falsch.
**Lösung:**
- Eigenen Error-Typ verwenden oder `IllegalArgumentException` (da interner Programmierfehler)
---
### N5 Fehlende Uniqueness-Validierung im Domain Layer
**Betroffene Datei:**
- `domain/usermanagement/User.java` (Zeilen 6469)
**Problem:**
Username- und Email-Uniqueness wird nur im Application Layer geprüft (Repository-Abfrage). Der Domain Layer dokumentiert diese Invariante nicht explizit.
**Lösung:**
- Domain Service für Uniqueness-Checks oder explizite Dokumentation als Application-Layer-Invariante
---
### N6 Repetitiver Switch-Pattern in Use Cases
**Betroffene Dateien:**
- Alle Use Cases im Application Layer
**Problem:**
Das Pattern `switch (repository.method()) { case Failure f -> ...; case Success s -> ...; }` wiederholt sich ~30+ Mal. Hoher Boilerplate-Anteil.
**Lösung:**
- `Result.flatMap()` und `Result.mapError()` konsequent nutzen
- Helper-Methode für Repository-Error → UserError Mapping
---
## Checkliste Production-Readiness
- [ ] K1: Aggregates immutable machen
- [ ] K2: Command-Validierung im Application Layer
- [ ] K3: AuthorizationPort in Use Cases integrieren
- [ ] K4: Token-Blacklist mit TTL/Redis ersetzen
- [ ] K5: Audit-Bug in ChangePassword fixen
- [ ] M4: Refresh Token implementieren
- [ ] M5: Rate Limiting einrichten
- [ ] M6: CORS konfigurieren
- [ ] N1: Swagger in Production absichern
- [ ] N2: Seed-Daten Production-safe machen
- [ ] JWT_SECRET, DB_PASSWORD als Environment-Variablen setzen
- [ ] HTTPS/TLS konfigurieren
- [ ] Log-Aggregation und Security-Monitoring aufsetzen

Some files were not shown because too many files have changed in this diff Show more