misc: Add documentation, clean up manage-db.sh
This commit is contained in:
538
CLAUDE.md
Normal file
538
CLAUDE.md
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
# Legal Consent Hub - Project Documentation
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**Legal Consent Hub** is a comprehensive platform for digital applications, approvals, and discussions, designed specifically for organizational workflows involving employee data processing and consent management. The platform enables organizations to create, manage, and track application forms with built-in compliance features for GDPR requirements.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
- **Dynamic Form Management**: Create and modify application forms with flexible, runtime-editable structures
|
||||||
|
- **Template System**: Reusable form templates for common use cases
|
||||||
|
- **Version Control**: Complete version history with snapshots for audit trails
|
||||||
|
- **PDF/HTML Export**: Generate professional PDF and HTML documents from forms
|
||||||
|
- **Collaborative Comments**: Attach comments to specific form elements for discussions
|
||||||
|
- **Smart Notifications**: User-specific, role-based, or organization-wide notifications
|
||||||
|
- **Multi-tenancy**: Organization-based data isolation
|
||||||
|
- **GDPR Compliance**: Built-in processing purposes and employee data categorization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### System Components
|
||||||
|
|
||||||
|
The application follows a **three-tier architecture**:
|
||||||
|
|
||||||
|
1. **Frontend** (`legalconsenthub/`): Nuxt 4 SSR application
|
||||||
|
2. **Backend** (`legalconsenthub-backend/`): Spring Boot 3 REST API with Kotlin
|
||||||
|
3. **Middleware** (`legalconsenthub-middleware/`): Client middleware layer (referenced but minimal presence in codebase)
|
||||||
|
|
||||||
|
### Technology Stack
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
- **Framework**: Nuxt 4.2.0 with Vue 3 (latest)
|
||||||
|
- **UI Library**: Nuxt UI 4.1.0 (Tailwind-based component library)
|
||||||
|
- **State Management**: Pinia 3.0.3
|
||||||
|
- **Internationalization**: @nuxtjs/i18n 10.0.3 (default: German, supports English)
|
||||||
|
- **Authentication**: nuxt-auth-utils 0.5.25 with OAuth2/Keycloak
|
||||||
|
- **Type Safety**: TypeScript 5.7.3
|
||||||
|
- **API Client**: Auto-generated from OpenAPI spec using openapi-generator-cli
|
||||||
|
|
||||||
|
#### Backend
|
||||||
|
- **Framework**: Spring Boot 3.4.2
|
||||||
|
- **Language**: Kotlin 1.9.25 with Java 21
|
||||||
|
- **ORM**: Spring Data JPA with Hibernate
|
||||||
|
- **Database**: PostgreSQL (with H2 for testing)
|
||||||
|
- **Database Migrations**: Liquibase
|
||||||
|
- **Authentication**: Spring Security with OAuth2 Resource Server (JWT)
|
||||||
|
- **API Documentation**: SpringDoc OpenAPI (Swagger UI)
|
||||||
|
- **PDF Generation**: OpenHTMLToPDF 1.0.10 with Thymeleaf templates
|
||||||
|
- **Testing**: Spring Boot Testcontainers
|
||||||
|
- **Code Quality**: ktlint 1.5.0
|
||||||
|
- **Build Tool**: Gradle
|
||||||
|
|
||||||
|
#### Infrastructure
|
||||||
|
- **Containerization**: Docker
|
||||||
|
- **Orchestration**: Docker Compose (dev and prod configurations)
|
||||||
|
- **CI/CD**: Gitea workflows (`.gitea/workflows/pipeline.yaml`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Domain Concepts
|
||||||
|
|
||||||
|
### 1. Organizations & Multi-tenancy
|
||||||
|
|
||||||
|
Users are assigned to **organizations**, which serve as the primary tenant boundary. Each organization:
|
||||||
|
- Has its own isolated set of application forms
|
||||||
|
- Has independent notification streams
|
||||||
|
- Has organization-specific roles and permissions
|
||||||
|
- Is identified by `organizationId` (string)
|
||||||
|
|
||||||
|
### 2. Application Forms
|
||||||
|
|
||||||
|
The central entity of the system. Application forms can be:
|
||||||
|
- **Templates** (`isTemplate: true`): Reusable form blueprints
|
||||||
|
- **Instances** (`isTemplate: false`): Actual forms being worked on
|
||||||
|
|
||||||
|
**Form Status Lifecycle**:
|
||||||
|
```
|
||||||
|
DRAFT → SUBMITTED → APPROVED/REJECTED → SIGNED
|
||||||
|
```
|
||||||
|
|
||||||
|
Each form has:
|
||||||
|
- Name and description
|
||||||
|
- Status tracking
|
||||||
|
- Audit fields (createdBy, lastModifiedBy, createdAt, modifiedAt)
|
||||||
|
- Organization association
|
||||||
|
- Hierarchical structure of form elements
|
||||||
|
|
||||||
|
### 3. Dynamic Form Structure
|
||||||
|
|
||||||
|
Forms follow a **three-level hierarchy**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Application Form
|
||||||
|
└── Section (FormElementSection)
|
||||||
|
├── title, shortTitle, description
|
||||||
|
└── SubSection (FormElementSubSection)
|
||||||
|
├── title, subtitle
|
||||||
|
└── Form Elements (FormElement)
|
||||||
|
├── type (SELECT, CHECKBOX, RADIOBUTTON, TEXTFIELD, SWITCH, TITLE_BODY_TEXTFIELDS)
|
||||||
|
├── title, description
|
||||||
|
└── options (FormOption[])
|
||||||
|
├── value, label
|
||||||
|
├── processingPurpose
|
||||||
|
└── employeeDataCategory
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dynamic Addition**: Users can add new form elements to any subsection at runtime via the API endpoint:
|
||||||
|
```
|
||||||
|
POST /application-forms/{applicationFormId}/subsections/{subsectionId}/form-elements?position={position}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Version History & Snapshots
|
||||||
|
|
||||||
|
Every update to an application form creates a new version:
|
||||||
|
- Automatic versioning on form updates
|
||||||
|
- Complete snapshots of form state (JSON-based)
|
||||||
|
- Ability to restore previous versions
|
||||||
|
- Audit trail with creator and timestamp
|
||||||
|
- Versions are immutable once created
|
||||||
|
|
||||||
|
### 5. PDF & HTML Generation
|
||||||
|
|
||||||
|
Forms can be exported as:
|
||||||
|
- **HTML**: Rendered using Thymeleaf templates (`templates/application_form_template.html`)
|
||||||
|
- **PDF**: Generated from HTML using OpenHTMLToPDF library
|
||||||
|
|
||||||
|
Endpoints:
|
||||||
|
- `GET /application-forms/{id}/html` - Returns HTML representation
|
||||||
|
- `GET /application-forms/{id}/pdf` - Returns PDF (inline display)
|
||||||
|
|
||||||
|
### 6. Comments System
|
||||||
|
|
||||||
|
Comments enable collaborative discussions:
|
||||||
|
- Attached to specific **form elements** (not sections/subsections)
|
||||||
|
- Associated with an application form
|
||||||
|
- Full CRUD operations
|
||||||
|
- Tracked with creator and timestamps
|
||||||
|
- Paginated retrieval per form
|
||||||
|
|
||||||
|
### 7. Notifications
|
||||||
|
|
||||||
|
Three types of notifications:
|
||||||
|
1. **User-specific**: Directed to a single user by `recipientId`
|
||||||
|
2. **Role-based**: Sent to all users with specific roles (`targetRoles[]`)
|
||||||
|
3. **Organization-wide**: Sent to all members (when both recipientId and targetRoles are null)
|
||||||
|
|
||||||
|
Notification types:
|
||||||
|
- `INFO`, `WARNING`, `ERROR`
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Mark individual/all as read
|
||||||
|
- Clear all notifications
|
||||||
|
- Get unread count (public endpoint for polling)
|
||||||
|
- Pagination support
|
||||||
|
|
||||||
|
### 8. GDPR Compliance Features
|
||||||
|
|
||||||
|
Each form option includes:
|
||||||
|
|
||||||
|
**Processing Purpose**:
|
||||||
|
- `SYSTEM_OPERATION`
|
||||||
|
- `BUSINESS_PROCESS`
|
||||||
|
- `DATA_ANALYSIS`
|
||||||
|
- `NONE`
|
||||||
|
|
||||||
|
**Employee Data Category**:
|
||||||
|
- `NON_CRITICAL`
|
||||||
|
- `REVIEW_REQUIRED`
|
||||||
|
- `SENSITIVE`
|
||||||
|
- `NONE`
|
||||||
|
|
||||||
|
These enable compliance tracking and risk assessment.
|
||||||
|
|
||||||
|
### 9. Role-Based Access Control
|
||||||
|
|
||||||
|
User roles in the system:
|
||||||
|
- `CHIEF_EXECUTIVE_OFFICER`
|
||||||
|
- `BUSINESS_DEPARTMENT`
|
||||||
|
- `IT_DEPARTMENT`
|
||||||
|
- `HUMAN_RESOURCES`
|
||||||
|
- `HEAD_OF_WORKS_COUNCIL`
|
||||||
|
- `WORKS_COUNCIL`
|
||||||
|
- `EMPLOYEE`
|
||||||
|
|
||||||
|
Roles are managed via Keycloak and enforced using Spring Security's `@PreAuthorize` annotations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Frontend Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
legalconsenthub/
|
||||||
|
├── app/
|
||||||
|
│ ├── app.config.ts # App-level configuration
|
||||||
|
│ ├── app.vue # Root component
|
||||||
|
│ ├── assets/css/ # Global styles
|
||||||
|
│ ├── components/ # Vue components
|
||||||
|
│ │ ├── formelements/ # Form input components
|
||||||
|
│ │ ├── DeleteModal.vue
|
||||||
|
│ │ ├── FormEngine.vue # Core form rendering engine
|
||||||
|
│ │ ├── FormStepperWithNavigation.vue
|
||||||
|
│ │ ├── FormValidationIndicator.vue
|
||||||
|
│ │ ├── NotificationsSlideover.vue
|
||||||
|
│ │ ├── RestoreVersionModal.vue
|
||||||
|
│ │ ├── ServerConnectionOverlay.vue
|
||||||
|
│ │ ├── TheComment.vue
|
||||||
|
│ │ ├── UserMenu.vue
|
||||||
|
│ │ ├── VersionComparisonModal.vue
|
||||||
|
│ │ └── VersionHistory.vue
|
||||||
|
│ ├── composables/ # Business logic composables
|
||||||
|
│ │ ├── applicationForm/
|
||||||
|
│ │ ├── applicationFormTemplate/
|
||||||
|
│ │ ├── applicationFormVersion/
|
||||||
|
│ │ ├── comment/
|
||||||
|
│ │ ├── notification/
|
||||||
|
│ │ ├── complianceMap.ts
|
||||||
|
│ │ ├── useApplicationFormNavigation.ts
|
||||||
|
│ │ ├── useApplicationFormValidator.ts
|
||||||
|
│ │ ├── useFormElementManagement.ts
|
||||||
|
│ │ ├── useFormStepper.ts
|
||||||
|
│ │ ├── usePermissions.ts
|
||||||
|
│ │ └── useServerHealth.ts
|
||||||
|
│ ├── layouts/ # Layout components
|
||||||
|
│ │ ├── auth.vue
|
||||||
|
│ │ └── default.vue
|
||||||
|
│ ├── middleware/ # Route middleware
|
||||||
|
│ │ ├── auth.global.ts # Authentication check
|
||||||
|
│ │ ├── permissions.global.ts # Authorization check
|
||||||
|
│ │ └── refreshToken.global.ts # Token refresh logic
|
||||||
|
│ ├── pages/ # Route pages
|
||||||
|
│ │ ├── application-forms/
|
||||||
|
│ │ ├── callback.vue # OAuth callback
|
||||||
|
│ │ ├── create.vue
|
||||||
|
│ │ ├── index.vue
|
||||||
|
│ │ └── login.vue
|
||||||
|
│ ├── plugins/ # Nuxt plugins
|
||||||
|
│ │ ├── error-handler.ts
|
||||||
|
│ │ └── server-health.client.ts
|
||||||
|
│ ├── stores/ # Pinia stores
|
||||||
|
│ │ ├── useCommentStore.ts
|
||||||
|
│ │ └── useUserStore.ts
|
||||||
|
│ └── utils/ # Utility functions
|
||||||
|
│ ├── date.ts
|
||||||
|
│ ├── formDiff.ts
|
||||||
|
│ └── wrappedFetch.ts
|
||||||
|
├── server/ # Nuxt server endpoints
|
||||||
|
│ ├── api/[...].ts # Proxy to backend API
|
||||||
|
│ └── routes/auth/ # OAuth routes
|
||||||
|
├── i18n/locales/ # Translation files
|
||||||
|
│ ├── de.json
|
||||||
|
│ └── en.json
|
||||||
|
├── nuxt.config.ts # Nuxt configuration
|
||||||
|
├── package.json
|
||||||
|
└── tsconfig.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
legalconsenthub-backend/
|
||||||
|
├── src/main/
|
||||||
|
│ ├── kotlin/com/betriebsratkanzlei/legalconsenthub/
|
||||||
|
│ │ ├── application_form/
|
||||||
|
│ │ │ ├── ApplicationForm.kt # Entity
|
||||||
|
│ │ │ ├── ApplicationFormController.kt # REST controller
|
||||||
|
│ │ │ ├── ApplicationFormFormatService.kt # PDF/HTML generation
|
||||||
|
│ │ │ ├── ApplicationFormMapper.kt # DTO ↔ Entity mapper
|
||||||
|
│ │ │ ├── ApplicationFormRepository.kt # JPA repository
|
||||||
|
│ │ │ ├── ApplicationFormService.kt # Business logic
|
||||||
|
│ │ │ └── PagedApplicationFormMapper.kt
|
||||||
|
│ │ ├── application_form_template/ # Template-specific logic
|
||||||
|
│ │ ├── application_form_version/ # Version management
|
||||||
|
│ │ ├── comment/ # Comment domain
|
||||||
|
│ │ ├── config/ # Security, TestContainers
|
||||||
|
│ │ ├── error/ # Custom exceptions & handler
|
||||||
|
│ │ ├── form_element/ # Form structure entities
|
||||||
|
│ │ │ ├── FormElement.kt
|
||||||
|
│ │ │ ├── FormElementSection.kt
|
||||||
|
│ │ │ ├── FormElementSubSection.kt
|
||||||
|
│ │ │ ├── FormOption.kt
|
||||||
|
│ │ │ └── [Mappers for each]
|
||||||
|
│ │ ├── notification/ # Notification domain
|
||||||
|
│ │ ├── security/ # JWT authentication
|
||||||
|
│ │ │ ├── CustomJwtAuthentication.kt
|
||||||
|
│ │ │ ├── CustomJwtAuthenticationConverter.kt
|
||||||
|
│ │ │ ├── CustomJwtTokenPrincipal.kt
|
||||||
|
│ │ │ └── JwtUserSyncFilter.kt
|
||||||
|
│ │ ├── user/ # User domain
|
||||||
|
│ │ └── LegalconsenthubApplication.kt # Spring Boot main
|
||||||
|
│ ├── resources/
|
||||||
|
│ │ ├── application.yaml # Main config
|
||||||
|
│ │ ├── application-h2.yaml # H2 profile
|
||||||
|
│ │ ├── application-testcontainers.yaml # Testcontainers profile
|
||||||
|
│ │ ├── db/changelog/ # Liquibase migrations
|
||||||
|
│ │ └── templates/
|
||||||
|
│ │ └── application_form_template.html # Thymeleaf template for PDF/HTML
|
||||||
|
│ └── test/
|
||||||
|
├── build.gradle # Gradle build config
|
||||||
|
├── Dockerfile
|
||||||
|
└── settings.gradle
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Specification
|
||||||
|
|
||||||
|
```
|
||||||
|
api/
|
||||||
|
├── legalconsenthub.yml # Main OpenAPI 3.0.3 spec
|
||||||
|
└── legalconsenthub-middleware.yml # Middleware API spec (if exists)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development Patterns & Conventions
|
||||||
|
|
||||||
|
### 1. API-First Development with OpenAPI
|
||||||
|
|
||||||
|
The project uses **OpenAPI 3.0.3** as the single source of truth:
|
||||||
|
|
||||||
|
**Frontend**:
|
||||||
|
```bash
|
||||||
|
pnpm run api:generate
|
||||||
|
# Generates TypeScript fetch clients to .api-client/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend**:
|
||||||
|
```gradle
|
||||||
|
./gradlew generate_legalconsenthub_server
|
||||||
|
# Generates Kotlin Spring interfaces to build/generated/server/
|
||||||
|
```
|
||||||
|
|
||||||
|
Controllers implement the generated API interfaces.
|
||||||
|
|
||||||
|
### 2. Mapper Pattern (Backend)
|
||||||
|
|
||||||
|
All DTO ↔ Entity conversions happen in separate **Mapper classes**:
|
||||||
|
- `ApplicationFormMapper`
|
||||||
|
- `FormElementMapper`
|
||||||
|
- `CommentMapper`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
**Rule**: Never perform mapping logic inside services or controllers.
|
||||||
|
|
||||||
|
### 3. Composables Pattern (Frontend)
|
||||||
|
|
||||||
|
Business logic is encapsulated in Vue composables:
|
||||||
|
- Grouped by domain (e.g., `composables/applicationForm/`)
|
||||||
|
- Exported through `composables/index.ts`
|
||||||
|
- Reusable across components
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `useApplicationFormNavigation()` - Form navigation logic
|
||||||
|
- `useApplicationFormValidator()` - Validation rules
|
||||||
|
- `useFormElementManagement()` - Dynamic form element operations
|
||||||
|
|
||||||
|
### 4. Manual Database Migrations
|
||||||
|
|
||||||
|
**Important**: Do NOT auto-generate SQL migrations.
|
||||||
|
|
||||||
|
Migrations are managed manually using **Liquibase** in `src/main/resources/db/changelog/`.
|
||||||
|
|
||||||
|
### 5. Code Quality & Linting
|
||||||
|
|
||||||
|
**Frontend**:
|
||||||
|
- ESLint with @nuxt/eslint
|
||||||
|
- Prettier for formatting
|
||||||
|
- TypeScript strict mode
|
||||||
|
|
||||||
|
**Backend**:
|
||||||
|
- ktlint for Kotlin formatting
|
||||||
|
- Gradle task: `./gradlew ktlintCheck`
|
||||||
|
|
||||||
|
### 6. Authentication Flow
|
||||||
|
|
||||||
|
1. User clicks login → Redirects to Keycloak
|
||||||
|
2. Keycloak authenticates → Returns to `/callback`
|
||||||
|
3. Backend exchanges code for JWT
|
||||||
|
4. JWT stored in secure cookie (`nuxt-auth-utils`)
|
||||||
|
5. Frontend middleware (`auth.global.ts`) checks auth on every route
|
||||||
|
6. Backend validates JWT on every API request
|
||||||
|
|
||||||
|
### 7. Error Handling
|
||||||
|
|
||||||
|
**Backend**: Custom exception classes with `@RestControllerAdvice` handler
|
||||||
|
**Frontend**: Global error handler plugin + local try/catch blocks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Endpoints Overview
|
||||||
|
|
||||||
|
### Application Forms
|
||||||
|
- `GET /application-forms` - List all forms (filtered by organizationId)
|
||||||
|
- `POST /application-forms` - Create new form
|
||||||
|
- `GET /application-forms/{id}` - Get form by ID
|
||||||
|
- `PUT /application-forms/{id}` - Update form
|
||||||
|
- `DELETE /application-forms/{id}` - Delete form
|
||||||
|
- `GET /application-forms/{id}/pdf` - Export as PDF
|
||||||
|
- `GET /application-forms/{id}/html` - Export as HTML
|
||||||
|
- `POST /application-forms/{id}/submit` - Submit form
|
||||||
|
- `POST /application-forms/{applicationFormId}/subsections/{subsectionId}/form-elements?position={n}` - Add form element dynamically
|
||||||
|
|
||||||
|
### Application Form Templates
|
||||||
|
- `GET /application-form-templates` - List templates
|
||||||
|
- `POST /application-form-templates` - Create template
|
||||||
|
- `GET /application-form-templates/{id}` - Get template
|
||||||
|
- `PUT /application-form-templates/{id}` - Update template
|
||||||
|
- `DELETE /application-form-templates/{id}` - Delete template
|
||||||
|
|
||||||
|
### Version History
|
||||||
|
- `GET /application-forms/{id}/versions` - List all versions
|
||||||
|
- `GET /application-forms/{id}/versions/{versionNumber}` - Get specific version
|
||||||
|
- `POST /application-forms/{id}/versions/{versionNumber}/restore` - Restore version
|
||||||
|
|
||||||
|
### Comments
|
||||||
|
- `GET /application-forms/{applicationFormId}/comments` - Get comments for form
|
||||||
|
- `POST /application-forms/{applicationFormId}/form-elements/{formElementId}/comments` - Create comment
|
||||||
|
- `PUT /comments/{id}` - Update comment
|
||||||
|
- `DELETE /comments/{id}` - Delete comment
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
- `GET /notifications?organizationId={id}` - Get notifications (paginated)
|
||||||
|
- `GET /notifications/unread?organizationId={id}` - Get unread notifications
|
||||||
|
- `GET /notifications/unread/count?userId={id}&organizationId={id}` - Get unread count (public)
|
||||||
|
- `POST /notifications` - Create notification
|
||||||
|
- `PUT /notifications/{id}/mark-read?organizationId={id}` - Mark as read
|
||||||
|
- `PUT /notifications/mark-all-read?organizationId={id}` - Mark all as read
|
||||||
|
- `DELETE /notifications/clear-all?organizationId={id}` - Clear all notifications
|
||||||
|
|
||||||
|
### Users
|
||||||
|
- `GET /users/{id}` - Get user by ID
|
||||||
|
- `DELETE /users/{id}` - Delete user
|
||||||
|
|
||||||
|
### Roles
|
||||||
|
- `GET /roles` - List all roles
|
||||||
|
- `POST /roles` - Create role
|
||||||
|
- `GET /roles/{id}` - Get role
|
||||||
|
- `DELETE /roles/{id}` - Delete role
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Frontend Environment Variables
|
||||||
|
|
||||||
|
Set in `nuxt.config.ts` → `runtimeConfig.public`:
|
||||||
|
|
||||||
|
- `clientProxyBasePath` - Frontend proxy path to backend
|
||||||
|
- `serverApiBaseUrl` - Direct backend URL (server-side)
|
||||||
|
- `serverApiBasePath` - Backend API base path
|
||||||
|
- `keycloakTokenUrl` - Keycloak token endpoint
|
||||||
|
|
||||||
|
OAuth configuration (`runtimeConfig.oauth.keycloak`):
|
||||||
|
- `clientId`, `clientSecret`, `realm`, `serverUrl`, `redirectURL`
|
||||||
|
- Scopes: `openid`, `organization`
|
||||||
|
|
||||||
|
### Backend Configuration
|
||||||
|
|
||||||
|
Main config in `src/main/resources/application.yaml`:
|
||||||
|
|
||||||
|
- Spring profiles: `h2`, `testcontainers`
|
||||||
|
- Database connection (PostgreSQL)
|
||||||
|
- Keycloak JWT validation
|
||||||
|
- Server port (default: 8080)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running the Application
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
**Prerequisites**:
|
||||||
|
- Node.js 22.16.0 (managed via Volta)
|
||||||
|
- pnpm 10.11.0+
|
||||||
|
- Java 21
|
||||||
|
- PostgreSQL (or use Docker)
|
||||||
|
|
||||||
|
**Frontend**:
|
||||||
|
```bash
|
||||||
|
cd legalconsenthub
|
||||||
|
pnpm install
|
||||||
|
pnpm run dev # Runs on port 3001
|
||||||
|
```
|
||||||
|
|
||||||
|
**Backend**:
|
||||||
|
```bash
|
||||||
|
cd legalconsenthub-backend
|
||||||
|
./gradlew bootRun # Runs on port 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
**Development**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f deployment/docker-compose-dev.yaml up
|
||||||
|
```
|
||||||
|
|
||||||
|
**Production**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f deployment/docker-compose-prod.yaml up
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- Type checking: `pnpm run type-check`
|
||||||
|
- Linting: `pnpm run lint`
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- Run tests: `./gradlew test`
|
||||||
|
- Uses Testcontainers for integration tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Notes for AI Assistants
|
||||||
|
|
||||||
|
1. **Never auto-generate SQL migrations** - Always prompt the developer to create them manually
|
||||||
|
2. **Use mapper classes** for all DTO/Entity conversions in backend
|
||||||
|
3. **Prefer existing types** - Don't create duplicate type definitions
|
||||||
|
4. **No redundant comments** - Don't add comments about what changed or redundant explanations
|
||||||
|
5. **Organization context** - Always consider organizationId for data isolation
|
||||||
|
6. **Form structure is three-level** - Section → SubSection → Element (not two-level)
|
||||||
|
7. **API generation is automatic** - After modifying OpenAPI spec, run generation commands
|
||||||
|
8. **Roles are managed in Keycloak** - Not in the application database
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- OpenAPI Specification: `api/legalconsenthub.yml`
|
||||||
|
- Thymeleaf Template: `legalconsenthub-backend/src/main/resources/templates/application_form_template.html`
|
||||||
|
- CI/CD Pipeline: `.gitea/workflows/pipeline.yaml`
|
||||||
|
- Database Management Script: `manage-db.sh`
|
||||||
|
- Test Data: `testdata.json`
|
||||||
|
- IT System Config: `it-system.json`
|
||||||
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Legal Consent Hub
|
||||||
|
|
||||||
|
A comprehensive platform for digital applications, approvals, and discussions, designed for organizational workflows involving employee data processing and consent management.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For detailed documentation, architecture, and development guidelines, see [claude.md](./claude.md).
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
### Pipeline Issues
|
||||||
|
|
||||||
|
- Gitea config can cause issues. When runner is not picking up jobs, run runner without config.
|
||||||
|
- Backend tests fail when a Postgres database is running on the host machine.
|
||||||
327
manage-db.sh
327
manage-db.sh
@@ -1,327 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Database Management Script
|
|
||||||
# Handles backup and restore operations for both SQLite (frontend) and PostgreSQL (backend)
|
|
||||||
|
|
||||||
set -e # Exit on any error
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
BACKEND_DIR="$SCRIPT_DIR/legalconsenthub-backend"
|
|
||||||
FRONTEND_DIR="$SCRIPT_DIR/legalconsenthub"
|
|
||||||
|
|
||||||
# Database files
|
|
||||||
SQLITE_DB="$FRONTEND_DIR/sqlite.db"
|
|
||||||
SQLITE_BACKUP="$FRONTEND_DIR/sqlite-backup.db"
|
|
||||||
POSTGRES_BACKUP="$BACKEND_DIR/src/main/resources/legalconsenthub-db-backup.sql"
|
|
||||||
|
|
||||||
# PostgreSQL connection details
|
|
||||||
POSTGRES_HOST="localhost"
|
|
||||||
POSTGRES_PORT="5432"
|
|
||||||
POSTGRES_DB="legalconsenthub"
|
|
||||||
POSTGRES_USER="legalconsenthub"
|
|
||||||
POSTGRES_PASSWORD="legalconsenthub"
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
log_info() {
|
|
||||||
echo -e "${BLUE}[INFO]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warning() {
|
|
||||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if PostgreSQL container is running
|
|
||||||
check_postgres() {
|
|
||||||
# Check if the legalconsenthub-backend container is running
|
|
||||||
if docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q "legalconsenthub-backend.*Up"; then
|
|
||||||
# Additional check to see if the database is ready
|
|
||||||
if docker exec legalconsenthub-backend pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" &>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if PostgreSQL container exists and is running
|
|
||||||
ensure_postgres_running() {
|
|
||||||
if ! check_postgres; then
|
|
||||||
log_error "PostgreSQL container 'legalconsenthub-backend' is not running."
|
|
||||||
log_error "Please start the container before running backup/restore operations."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create backup of current SQLite database
|
|
||||||
backup_sqlite() {
|
|
||||||
log_info "Creating SQLite backup..."
|
|
||||||
if [ -f "$SQLITE_DB" ]; then
|
|
||||||
cp "$SQLITE_DB" "$SQLITE_BACKUP"
|
|
||||||
log_success "SQLite backup created: $SQLITE_BACKUP"
|
|
||||||
else
|
|
||||||
log_warning "SQLite database not found: $SQLITE_DB"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create backup of current PostgreSQL database
|
|
||||||
backup_postgres() {
|
|
||||||
log_info "Creating PostgreSQL backup..."
|
|
||||||
ensure_postgres_running
|
|
||||||
|
|
||||||
# Create temporary backup file
|
|
||||||
local temp_backup="${POSTGRES_BACKUP}.tmp"
|
|
||||||
|
|
||||||
# Generate backup with pg_dump
|
|
||||||
docker exec legalconsenthub-backend pg_dump \
|
|
||||||
-U "$POSTGRES_USER" \
|
|
||||||
-d "$POSTGRES_DB" \
|
|
||||||
--no-owner \
|
|
||||||
--no-privileges \
|
|
||||||
--clean \
|
|
||||||
--if-exists \
|
|
||||||
> "$temp_backup"
|
|
||||||
|
|
||||||
# Add force DROP statements at the beginning
|
|
||||||
{
|
|
||||||
echo "--"
|
|
||||||
echo "-- PostgreSQL database dump"
|
|
||||||
echo "--"
|
|
||||||
echo ""
|
|
||||||
echo "--"
|
|
||||||
echo "-- Force drop all tables and constraints to ensure clean restoration"
|
|
||||||
echo "--"
|
|
||||||
echo ""
|
|
||||||
echo "DROP TABLE IF EXISTS public.user_organization_roles CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.notification CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.form_element_section CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.form_element_options CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.form_element CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.comment CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.application_form CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.app_user CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.databasechangeloglock CASCADE;"
|
|
||||||
echo "DROP TABLE IF EXISTS public.databasechangelog CASCADE;"
|
|
||||||
echo ""
|
|
||||||
echo "-- Drop any remaining sequences, views, or other objects"
|
|
||||||
echo "DROP SEQUENCE IF EXISTS public.hibernate_sequence CASCADE;"
|
|
||||||
echo ""
|
|
||||||
echo "-- Reset database configuration"
|
|
||||||
|
|
||||||
# Skip the first few lines of the original dump (headers) and append the rest
|
|
||||||
tail -n +4 "$temp_backup"
|
|
||||||
} > "$POSTGRES_BACKUP"
|
|
||||||
|
|
||||||
# Remove temporary file
|
|
||||||
rm "$temp_backup"
|
|
||||||
|
|
||||||
log_success "PostgreSQL backup created with force DROP statements: $POSTGRES_BACKUP"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Restore SQLite database from backup
|
|
||||||
restore_sqlite() {
|
|
||||||
log_info "Restoring SQLite database from backup..."
|
|
||||||
if [ -f "$SQLITE_BACKUP" ]; then
|
|
||||||
# Remove current database if it exists
|
|
||||||
if [ -f "$SQLITE_DB" ]; then
|
|
||||||
rm "$SQLITE_DB"
|
|
||||||
log_info "Removed current SQLite database"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy backup to current database
|
|
||||||
cp "$SQLITE_BACKUP" "$SQLITE_DB"
|
|
||||||
log_success "SQLite database restored from: $SQLITE_BACKUP"
|
|
||||||
else
|
|
||||||
log_error "SQLite backup not found: $SQLITE_BACKUP"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Restore PostgreSQL database from backup
|
|
||||||
restore_postgres() {
|
|
||||||
log_info "Restoring PostgreSQL database from backup..."
|
|
||||||
|
|
||||||
if [ ! -f "$POSTGRES_BACKUP" ]; then
|
|
||||||
log_error "PostgreSQL backup not found: $POSTGRES_BACKUP"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ensure_postgres_running
|
|
||||||
|
|
||||||
# Terminate all connections and drop/recreate database
|
|
||||||
log_info "Terminating all connections to PostgreSQL database..."
|
|
||||||
docker exec legalconsenthub-backend psql \
|
|
||||||
-U "$POSTGRES_USER" \
|
|
||||||
-d "postgres" \
|
|
||||||
-c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$POSTGRES_DB' AND pid <> pg_backend_pid();"
|
|
||||||
|
|
||||||
log_info "Dropping and recreating PostgreSQL database..."
|
|
||||||
docker exec legalconsenthub-backend psql \
|
|
||||||
-U "$POSTGRES_USER" \
|
|
||||||
-d "postgres" \
|
|
||||||
-c "DROP DATABASE IF EXISTS $POSTGRES_DB;"
|
|
||||||
|
|
||||||
docker exec legalconsenthub-backend psql \
|
|
||||||
-U "$POSTGRES_USER" \
|
|
||||||
-d "postgres" \
|
|
||||||
-c "CREATE DATABASE $POSTGRES_DB OWNER $POSTGRES_USER;"
|
|
||||||
|
|
||||||
# Restore from backup
|
|
||||||
log_info "Restoring database from backup file..."
|
|
||||||
docker exec -i legalconsenthub-backend psql \
|
|
||||||
-U "$POSTGRES_USER" \
|
|
||||||
-d "$POSTGRES_DB" \
|
|
||||||
< "$POSTGRES_BACKUP"
|
|
||||||
|
|
||||||
log_success "PostgreSQL database restored from: $POSTGRES_BACKUP"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Show usage information
|
|
||||||
show_usage() {
|
|
||||||
echo "Database Management Script"
|
|
||||||
echo ""
|
|
||||||
echo "Usage: $0 [COMMAND]"
|
|
||||||
echo ""
|
|
||||||
echo "Commands:"
|
|
||||||
echo " backup Create backups of current databases"
|
|
||||||
echo " restore Restore databases from backups"
|
|
||||||
echo " backup-sqlite Create backup of SQLite database only"
|
|
||||||
echo " backup-postgres Create backup of PostgreSQL database only"
|
|
||||||
echo " restore-sqlite Restore SQLite database from backup only"
|
|
||||||
echo " restore-postgres Restore PostgreSQL database from backup only"
|
|
||||||
echo " status Show current status"
|
|
||||||
echo " help Show this help message"
|
|
||||||
echo ""
|
|
||||||
echo "Note: PostgreSQL operations require the 'legalconsenthub-backend' container to be running."
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 backup # Create backups of both databases"
|
|
||||||
echo " $0 restore # Restore both databases from backups"
|
|
||||||
echo " $0 backup-sqlite # Backup only SQLite database"
|
|
||||||
echo " $0 status # Check database status"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Show current status
|
|
||||||
show_status() {
|
|
||||||
log_info "Database Status:"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# SQLite status
|
|
||||||
echo "SQLite Database:"
|
|
||||||
if [ -f "$SQLITE_DB" ]; then
|
|
||||||
local size=$(du -h "$SQLITE_DB" | cut -f1)
|
|
||||||
local modified=$(stat -c "%y" "$SQLITE_DB" 2>/dev/null || stat -f "%Sm" "$SQLITE_DB" 2>/dev/null || echo "Unknown")
|
|
||||||
echo " Current DB: ✅ $SQLITE_DB ($size, modified: $modified)"
|
|
||||||
else
|
|
||||||
echo " Current DB: ❌ Not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$SQLITE_BACKUP" ]; then
|
|
||||||
local backup_size=$(du -h "$SQLITE_BACKUP" | cut -f1)
|
|
||||||
local backup_modified=$(stat -c "%y" "$SQLITE_BACKUP" 2>/dev/null || stat -f "%Sm" "$SQLITE_BACKUP" 2>/dev/null || echo "Unknown")
|
|
||||||
echo " Backup: ✅ $SQLITE_BACKUP ($backup_size, modified: $backup_modified)"
|
|
||||||
else
|
|
||||||
echo " Backup: ❌ Not found"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# PostgreSQL status
|
|
||||||
echo "PostgreSQL Database:"
|
|
||||||
if check_postgres; then
|
|
||||||
echo " Container: ✅ legalconsenthub-backend (Running)"
|
|
||||||
local db_size=$(docker exec legalconsenthub-backend psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t -c "SELECT pg_size_pretty(pg_database_size('$POSTGRES_DB'));" 2>/dev/null | xargs || echo "Unknown")
|
|
||||||
echo " Database: ✅ $POSTGRES_DB ($db_size)"
|
|
||||||
else
|
|
||||||
echo " Container: ❌ legalconsenthub-backend (Not running or not found)"
|
|
||||||
echo " Database: ❓ Cannot check (container not available)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$POSTGRES_BACKUP" ]; then
|
|
||||||
local backup_size=$(du -h "$POSTGRES_BACKUP" | cut -f1)
|
|
||||||
local backup_modified=$(stat -c "%y" "$POSTGRES_BACKUP" 2>/dev/null || stat -f "%Sm" "$POSTGRES_BACKUP" 2>/dev/null || echo "Unknown")
|
|
||||||
echo " Backup: ✅ $POSTGRES_BACKUP ($backup_size, modified: $backup_modified)"
|
|
||||||
else
|
|
||||||
echo " Backup: ❌ Not found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main script logic
|
|
||||||
main() {
|
|
||||||
case "${1:-help}" in
|
|
||||||
"backup")
|
|
||||||
log_info "Creating backups of both databases..."
|
|
||||||
backup_sqlite
|
|
||||||
backup_postgres
|
|
||||||
log_success "All backups completed successfully!"
|
|
||||||
;;
|
|
||||||
"restore")
|
|
||||||
log_info "Restoring both databases from backups..."
|
|
||||||
restore_sqlite
|
|
||||||
restore_postgres
|
|
||||||
log_success "All databases restored successfully!"
|
|
||||||
;;
|
|
||||||
"backup-sqlite")
|
|
||||||
backup_sqlite
|
|
||||||
;;
|
|
||||||
"backup-postgres")
|
|
||||||
backup_postgres
|
|
||||||
;;
|
|
||||||
"restore-sqlite")
|
|
||||||
restore_sqlite
|
|
||||||
;;
|
|
||||||
"restore-postgres")
|
|
||||||
restore_postgres
|
|
||||||
;;
|
|
||||||
"status")
|
|
||||||
show_status
|
|
||||||
;;
|
|
||||||
"help"|"-h"|"--help")
|
|
||||||
show_usage
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log_error "Unknown command: $1"
|
|
||||||
echo ""
|
|
||||||
show_usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for required tools
|
|
||||||
check_dependencies() {
|
|
||||||
local missing_tools=()
|
|
||||||
|
|
||||||
# Check for Docker
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
missing_tools+=("docker")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ${#missing_tools[@]} -gt 0 ]; then
|
|
||||||
log_error "Missing required tools:"
|
|
||||||
for tool in "${missing_tools[@]}"; do
|
|
||||||
echo " - $tool"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo "Please install the missing tools and try again."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run dependency check and main function
|
|
||||||
check_dependencies
|
|
||||||
main "$@"
|
|
||||||
Reference in New Issue
Block a user