Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions web-backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,19 @@ local.properties
google-services.json
target
/src/main/frontend/node_modules/

# Generated CSS from Tailwind build
/src/main/resources/static/main.css

# SQLite database files
*.db
*.db-journal
*.db-wal
*.db-shm

### VS Code ###
.vscode/

### JTE ###
/jte-classes/

3 changes: 2 additions & 1 deletion web-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ Check out the [docs](docs/) and [ADRs](docs/adr/README.md)
The project uses Spring Boot 3.4.5 with the following key configurations:

1. **Database**: PostgreSQL is used as the primary database
- **Optional**: SQLite can be used for development/testing (see [SQLite Persistence](docs/sqlite-persistence.md))
2. **Template Engine**: JTE (Java Template Engine) is used for HTML templates
3. **Authentication**: Azure AD integration via Spring Security OAuth2
4. **File Upload**: Apache POI for Excel file handling

Required environment variables:
Required environment variables (for PostgreSQL):
- `SPRING_DATASOURCE_URL`: Database connection URL
- `SPRING_DATASOURCE_USERNAME`: Database username
- `SPRING_DATASOURCE_PASSWORD`: Database password
Expand Down
90 changes: 90 additions & 0 deletions web-backend/docs/adr/0004-optional-sqlite-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Architecture Decision Record

## Title
Add Optional SQLite Persistence Layer

## Status
Accepted

## Context
While PostgreSQL with JPA is the primary production database, there are scenarios where developers and testers may benefit from a lightweight, file-based database option:

1. **Quick Local Development**: Developers can run the application without setting up PostgreSQL
2. **Simplified Testing**: Integration tests can use SQLite without Docker/TestContainers overhead
3. **Demos and Prototyping**: Easier to share and demonstrate features without infrastructure setup
4. **CI/CD Optimization**: Faster builds in certain scenarios

However, SQLite has limitations:
- Limited concurrency support
- No advanced PostgreSQL-specific features (JSONB, advanced indexing)
- Different SQL dialect quirks
- Not suitable for production workloads

## Decision
We will add an optional SQLite persistence layer that:

1. **Remains Disabled by Default**: PostgreSQL/JPA remains the default configuration
2. **Profile-Based Activation**: SQLite is enabled via Spring profile (`sqlite`) AND a specific property (`vms.persistence.sqlite.enabled=true`)
3. **Maintains JPA Compatibility**: Uses the same JPA entities and repositories
4. **Leverages Hibernate Community Dialects**: Uses `org.hibernate.community.dialect.SQLiteDialect`
5. **Disables Flyway**: SQLite relies on Hibernate's DDL auto-generation instead of migrations

## Implementation Details

### Dependencies
- `sqlite-jdbc` (3.47.2.0): JDBC driver for SQLite
- `hibernate-community-dialects`: Provides SQLite dialect for Hibernate

### Configuration
- Profile: `sqlite` in `application-sqlite.yml`
- Property gate: `vms.persistence.sqlite.enabled=true`
- Database file: `vms.db` (created in working directory)
- Hibernate DDL: `update` mode (auto-create/update schema)

### Activation
To use SQLite, both conditions must be met:
1. Profile: `--spring.profiles.active=sqlite`
2. Property: `vms.persistence.sqlite.enabled=true`

Example:
```bash
./mvnw spring-boot:run -Dspring-boot.run.profiles=sqlite -Dspring-boot.run.arguments="--vms.persistence.sqlite.enabled=true"
```

## Consequences

### Positive
- Developers can quickly test without PostgreSQL setup
- Simpler demo and prototyping scenarios
- Potential CI/CD time savings for certain test scenarios
- Maintains full JPA compatibility (same entities and repositories work)

### Negative
- Additional dependency to maintain
- SQLite behavior differences may mask PostgreSQL-specific issues
- Developers might accidentally develop against SQLite instead of production DB
- Requires explicit double opt-in to prevent accidental usage

### Neutral
- Configuration complexity slightly increased
- Need to ensure production deployments never accidentally enable SQLite

## Alternatives Considered

### H2 Database
- **Pro**: More commonly used in Spring Boot projects, better PostgreSQL compatibility mode
- **Con**: Heavier weight than SQLite, still has dialect differences

### TestContainers Only
- **Pro**: Exact PostgreSQL match, no dialect issues
- **Con**: Requires Docker, slower startup, more complex setup

### Keep PostgreSQL Only
- **Pro**: Single database to support, no dialect confusion
- **Con**: Higher barrier to entry for new developers, slower local development setup

## Notes
- SQLite is **NOT** for production use
- All CI/CD production tests should continue using PostgreSQL via TestContainers
- This option is primarily for developer convenience and specific testing scenarios
- Production configuration remains unchanged (PostgreSQL/JPA)
190 changes: 190 additions & 0 deletions web-backend/docs/sqlite-persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# SQLite Persistence Layer

## Overview
This directory contains documentation for the optional SQLite persistence layer for the VMS application. SQLite is provided as an alternative to PostgreSQL for development, testing, and demonstration purposes.

## ⚠️ Important Notice
**SQLite is NOT intended for production use.** PostgreSQL with JPA remains the primary and recommended database for all production environments.

## When to Use SQLite

### Good Use Cases
- **Quick Local Development**: Testing features without PostgreSQL setup
- **Simple Demos**: Demonstrating functionality with minimal infrastructure
- **Unit/Integration Testing**: Faster tests without Docker overhead
- **Prototyping**: Rapid experimentation with new features

### Bad Use Cases
- **Production Deployments**: Never use SQLite in production
- **Performance Testing**: SQLite performance != PostgreSQL performance
- **Concurrent Access**: SQLite has limited concurrency support
- **PostgreSQL-Specific Features**: Testing JSONB, advanced indexes, etc.

## Enabling SQLite

### Prerequisites
SQLite is built into the dependencies. No additional installation required.

### Activation
SQLite requires **both** conditions to be met:

1. **Spring Profile**: `sqlite`
2. **Configuration Property**: `vms.persistence.sqlite.enabled=true`

### Option 1: Command Line
```bash
./mvnw spring-boot:run \
-Dspring-boot.run.profiles=sqlite \
-Dspring-boot.run.arguments="--vms.persistence.sqlite.enabled=true"
```

### Option 2: Environment Variables
```bash
export SPRING_PROFILES_ACTIVE=sqlite
export VMS_PERSISTENCE_SQLITE_ENABLED=true
./mvnw spring-boot:run
```

### Option 3: IDE Configuration (IntelliJ IDEA)
1. Edit Run Configuration
2. Set **Active profiles**: `sqlite`
3. Add **VM options** or **Program arguments**: `--vms.persistence.sqlite.enabled=true`
4. Run the application

### Option 4: Application Properties (Not Recommended)
In `application.yml`:
```yaml
spring:
profiles:
active: sqlite

vms:
persistence:
sqlite:
enabled: true
```

**Warning**: This permanently enables SQLite. Use command line or environment variables instead.

## Configuration Details

### Database File
- **Location**: `vms.db` in the working directory (where you run the application)
- **Auto-created**: The file is created automatically on first run
- **Reset**: Delete `vms.db` to reset the database

### Schema Management
- **Hibernate DDL**: `update` mode (auto-create/update tables)
- **Flyway**: Disabled (SQLite uses Hibernate DDL instead)
- **Migrations**: Not applied in SQLite mode

### Dialect
- **Hibernate Dialect**: `org.hibernate.community.dialect.SQLiteDialect`
- **JDBC Driver**: `org.sqlite.JDBC`

## Limitations

### Functional Limitations
1. **No Concurrent Writes**: SQLite locks the entire database for writes
2. **Limited ALTER TABLE**: Some schema changes require table recreation
3. **No Advanced Features**: No JSONB, advanced indexing, or PostgreSQL extensions
4. **Different SQL Dialect**: Some queries may behave differently

### Development Considerations
1. **Dialect Differences**: Code that works on SQLite might fail on PostgreSQL
2. **Performance**: SQLite performance is not representative of PostgreSQL
3. **Data Types**: Some type mappings differ between SQLite and PostgreSQL
4. **Transaction Behavior**: Different isolation and locking semantics

## Switching Back to PostgreSQL

### Option 1: Remove Profile
Simply run without the `sqlite` profile:
```bash
./mvnw spring-boot:run
```

### Option 2: Unset Environment Variables
```bash
unset SPRING_PROFILES_ACTIVE
unset VMS_PERSISTENCE_SQLITE_ENABLED
./mvnw spring-boot:run
```

### Verify PostgreSQL is Active
Check the logs on startup:
```
Hibernate: Using dialect: org.hibernate.dialect.PostgreSQLDialect
```

If you see `SQLiteDialect` instead, SQLite is still active.

## Testing with SQLite

### Unit Tests
Create a test configuration with SQLite profile:

```java
@SpringBootTest
@ActiveProfiles("sqlite")
@TestPropertySource(properties = {
"vms.persistence.sqlite.enabled=true"
})
class MyServiceTest {
// Tests here
}
```

### Integration Tests
For true integration testing, prefer TestContainers with PostgreSQL:
```java
@SpringBootTest
@Testcontainers
@Import(TestcontainersConfiguration.class)
class MyIntegrationTest {
// Tests with actual PostgreSQL
}
```

## Troubleshooting

### SQLite Not Activating
**Problem**: Application still uses PostgreSQL

**Solutions**:
1. Verify both profile AND property are set
2. Check logs for: `Using dialect: SQLiteDialect`
3. Ensure no conflicting datasource configuration

### "Database Locked" Errors
**Problem**: `SQLiteException: database is locked`

**Solutions**:
1. Close other connections to `vms.db`
2. Stop other running instances of the application
3. Delete `vms.db` and restart (loses data)
4. SQLite may not be suitable for your concurrency needs

### Schema Issues
**Problem**: Tables not created or migrations failing

**Solutions**:
1. Verify Flyway is disabled (should be in `application-sqlite.yml`)
2. Check Hibernate DDL is set to `update`
3. Delete `vms.db` to recreate schema from scratch
4. SQLite has limited ALTER TABLE support - may need to recreate tables

### Switching Between Databases
**Problem**: Data not visible after switching

**Remember**:
- PostgreSQL data is in PostgreSQL (Docker/remote server)
- SQLite data is in `vms.db` file
- They are separate databases with separate data

## Architecture Decision Record
For the full rationale and decision details, see:
[ADR-0004: Optional SQLite Persistence](../docs/adr/0004-optional-sqlite-persistence.md)

## Support
SQLite support is provided as-is for development convenience. For production issues, always test against PostgreSQL first.
9 changes: 9 additions & 0 deletions web-backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.47.2.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.statusneo.vms.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

/**
* Configuration class for SQLite persistence layer.
* This configuration is only active when the 'sqlite' profile is enabled.
*
* To enable SQLite persistence:
* - Set spring.profiles.active=sqlite in application.yml, or
* - Use --spring.profiles.active=sqlite as a command line argument, or
* - Set SPRING_PROFILES_ACTIVE=sqlite as an environment variable
*
* Note: SQLite is intended for development and testing purposes.
* For production, PostgreSQL with JPA should be used.
*/
@Configuration
@Profile("sqlite")
@ConditionalOnProperty(name = "vms.persistence.sqlite.enabled", havingValue = "true", matchIfMissing = false)
public class SQLiteConfig {

/**
* Default constructor.
* This configuration class uses Spring Boot auto-configuration
* with the settings from application-sqlite.yml.
*/
public SQLiteConfig() {
// SQLite configuration is driven by application-sqlite.yml
// This class serves as a marker for the SQLite profile
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,6 @@ public ResponseEntity<String> refreshEmployeeCache() {
return ResponseEntity.ok("Cache refreshed");
}

@PostMapping("/sync-employees")
public ResponseEntity<String> syncEmployees() {
int count = graphDirectoryService.syncAllUsersToEmployees();
return ResponseEntity.ok("Synced " + count + " employees from Office365");
}

@PostMapping("/register")
public String registerVisitor(@ModelAttribute Visitor visitor,
@RequestParam(value = "host", required = false) String host,
Expand Down
Loading
Loading