From faa38dd354464c6e56566f54f58da114ca7735ac Mon Sep 17 00:00:00 2001 From: mgrablo <51509107+Michauu035@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:25:50 +0100 Subject: [PATCH 1/5] refactor: update LoanProperties to use record type and simplify LoanServiceImpl --- .../mgrablo/BiblioNode/BiblioNodeApplication.java | 4 ++++ .../mgrablo/BiblioNode/config/LoanProperties.java | 13 ++++--------- .../mgrablo/BiblioNode/service/LoanServiceImpl.java | 6 +++--- .../BiblioNode/service/LoanServiceImplTest.java | 4 +--- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java b/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java index 15c430c..e4e7870 100644 --- a/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java +++ b/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java @@ -2,8 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +import io.github.mgrablo.BiblioNode.config.LoanProperties; @SpringBootApplication +@EnableConfigurationProperties(LoanProperties.class) public class BiblioNodeApplication { public static void main(String[] args) { diff --git a/src/main/java/io/github/mgrablo/BiblioNode/config/LoanProperties.java b/src/main/java/io/github/mgrablo/BiblioNode/config/LoanProperties.java index 76cb793..db033cd 100644 --- a/src/main/java/io/github/mgrablo/BiblioNode/config/LoanProperties.java +++ b/src/main/java/io/github/mgrablo/BiblioNode/config/LoanProperties.java @@ -1,15 +1,10 @@ package io.github.mgrablo.BiblioNode.config; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import lombok.Getter; -import lombok.Setter; - -@Configuration @ConfigurationProperties(prefix = "app.loan") -@Getter @Setter -public class LoanProperties { - private int maxActiveLoans = 5; - private int defaultLoanDays = 14; +public record LoanProperties( + int maxActiveLoans, + int defaultLoanDays +) { } \ No newline at end of file diff --git a/src/main/java/io/github/mgrablo/BiblioNode/service/LoanServiceImpl.java b/src/main/java/io/github/mgrablo/BiblioNode/service/LoanServiceImpl.java index 8d3a677..a20b859 100644 --- a/src/main/java/io/github/mgrablo/BiblioNode/service/LoanServiceImpl.java +++ b/src/main/java/io/github/mgrablo/BiblioNode/service/LoanServiceImpl.java @@ -50,8 +50,8 @@ public LoanResponse borrowBook(BorrowRequest request, String email) { .orElseThrow(() -> new ResourceNotFoundException("Reader not found")); Long activeLoansCount = loanRepository.countByReaderIdAndReturnDateIsNull(reader.getId()); - if (activeLoansCount >= loanProperties.getMaxActiveLoans()) { - throw new LoanLimitExceededException("Reader has exceeded the maximum number of active loans (" + loanProperties.getMaxActiveLoans() + ")"); + if (activeLoansCount >= loanProperties.maxActiveLoans()) { + throw new LoanLimitExceededException("Reader has exceeded the maximum number of active loans (" + loanProperties.maxActiveLoans() + ")"); } book.setAvailable(false); @@ -59,7 +59,7 @@ public LoanResponse borrowBook(BorrowRequest request, String email) { LocalDateTime now = LocalDateTime.now(clock); Loan loan = new Loan(); loan.setLoanDate(now); - loan.setDueDate(now.plusDays(loanProperties.getDefaultLoanDays())); + loan.setDueDate(now.plusDays(loanProperties.defaultLoanDays())); loan.setBook(book); loan.setReader(reader); diff --git a/src/test/java/io/github/mgrablo/BiblioNode/service/LoanServiceImplTest.java b/src/test/java/io/github/mgrablo/BiblioNode/service/LoanServiceImplTest.java index 019f843..7ead18b 100644 --- a/src/test/java/io/github/mgrablo/BiblioNode/service/LoanServiceImplTest.java +++ b/src/test/java/io/github/mgrablo/BiblioNode/service/LoanServiceImplTest.java @@ -59,9 +59,7 @@ public class LoanServiceImplTest { @BeforeEach void setup() { - LoanProperties loanProperties = new LoanProperties(); - loanProperties.setMaxActiveLoans(5); - loanProperties.setDefaultLoanDays(14); + LoanProperties loanProperties = new LoanProperties(5, 14); fixedClock = Clock.fixed(fixedInstant, zoneId); loanService = new LoanServiceImpl(loanRepository, From 37a8ac84b6963f6beb11749a10b9c24b136ce654 Mon Sep 17 00:00:00 2001 From: mgrablo <51509107+Michauu035@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:39:37 +0100 Subject: [PATCH 2/5] feat: externalize JWT expiration configuration and add SecurityProperties --- .../BiblioNode/BiblioNodeApplication.java | 3 ++- .../BiblioNode/config/SecurityProperties.java | 8 ++++++++ .../BiblioNode/service/AuthServiceImpl.java | 4 +++- src/main/resources/application-dev.yaml | 2 ++ src/main/resources/application.yaml | 2 ++ .../BiblioNode/service/AuthServiceImplTest.java | 16 ++++++++++++++++ src/test/resources/application-test.yaml | 2 ++ 7 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/github/mgrablo/BiblioNode/config/SecurityProperties.java diff --git a/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java b/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java index e4e7870..826dc0e 100644 --- a/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java +++ b/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java @@ -5,9 +5,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import io.github.mgrablo.BiblioNode.config.LoanProperties; +import io.github.mgrablo.BiblioNode.config.SecurityProperties; @SpringBootApplication -@EnableConfigurationProperties(LoanProperties.class) +@EnableConfigurationProperties({LoanProperties.class, SecurityProperties.class}) public class BiblioNodeApplication { public static void main(String[] args) { diff --git a/src/main/java/io/github/mgrablo/BiblioNode/config/SecurityProperties.java b/src/main/java/io/github/mgrablo/BiblioNode/config/SecurityProperties.java new file mode 100644 index 0000000..e538558 --- /dev/null +++ b/src/main/java/io/github/mgrablo/BiblioNode/config/SecurityProperties.java @@ -0,0 +1,8 @@ +package io.github.mgrablo.BiblioNode.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "app.security") +public record SecurityProperties( + int jwtExpirationHours +) { } diff --git a/src/main/java/io/github/mgrablo/BiblioNode/service/AuthServiceImpl.java b/src/main/java/io/github/mgrablo/BiblioNode/service/AuthServiceImpl.java index c669954..bad8eab 100644 --- a/src/main/java/io/github/mgrablo/BiblioNode/service/AuthServiceImpl.java +++ b/src/main/java/io/github/mgrablo/BiblioNode/service/AuthServiceImpl.java @@ -12,6 +12,7 @@ import java.time.temporal.ChronoUnit; import java.util.stream.Collectors; +import io.github.mgrablo.BiblioNode.config.SecurityProperties; import io.github.mgrablo.BiblioNode.dto.*; import io.github.mgrablo.BiblioNode.model.User; import io.github.mgrablo.BiblioNode.repository.UserRepository; @@ -26,6 +27,7 @@ public class AuthServiceImpl implements AuthService { private final ReaderService readerService; private final PasswordEncoder passwordEncoder; private final JwtEncoder jwtEncoder; + private final SecurityProperties securityProperties; @Override public ReaderResponse register(RegisterRequest request) { @@ -52,7 +54,7 @@ public LoginResponse login(LoginRequest request) { JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("BiblioNode") .issuedAt(now) - .expiresAt(now.plus(1, ChronoUnit.HOURS)) + .expiresAt(now.plus(securityProperties.jwtExpirationHours(), ChronoUnit.HOURS)) .subject(user.getEmail()) .claim("roles", scope) .build(); diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 930978c..43335f9 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -8,6 +8,8 @@ app: loan: max-active-loans: 5 default-loan-days: 21 + security: + jwt-expiration-hours: 24 spring: application: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 354489f..466eb54 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -8,6 +8,8 @@ app: loan: max-active-loans: 5 default-loan-days: 14 + security: + jwt-expiration-hours: 1 spring: application: diff --git a/src/test/java/io/github/mgrablo/BiblioNode/service/AuthServiceImplTest.java b/src/test/java/io/github/mgrablo/BiblioNode/service/AuthServiceImplTest.java index 0be5896..62d4120 100644 --- a/src/test/java/io/github/mgrablo/BiblioNode/service/AuthServiceImplTest.java +++ b/src/test/java/io/github/mgrablo/BiblioNode/service/AuthServiceImplTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -20,6 +21,7 @@ import java.util.Optional; import java.util.Set; +import io.github.mgrablo.BiblioNode.config.SecurityProperties; import io.github.mgrablo.BiblioNode.dto.*; import io.github.mgrablo.BiblioNode.exception.DataIntegrityException; import io.github.mgrablo.BiblioNode.model.Role; @@ -43,6 +45,20 @@ class AuthServiceImplTest { @InjectMocks private AuthServiceImpl authService; + @BeforeEach + void setup() { + SecurityProperties securityProperties = new SecurityProperties(1); + + authService = new AuthServiceImpl( + userService, + userRepository, + readerService, + passwordEncoder, + jwtEncoder, + securityProperties + ); + } + @Test void register_ShouldCoordinateUserAndProfileCreation() { RegisterRequest request = new RegisterRequest("test@email.com", "password", "Test User"); diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index f42d691..90c90df 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -8,6 +8,8 @@ app: loan: max-active-loans: 2 default-loan-days: 14 + security: + jwt-expiration-hours: 24 spring: datasource: From b89affbb91679a64bbf7ad879c885d1e80dc6baa Mon Sep 17 00:00:00 2001 From: mgrablo <51509107+Michauu035@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:15:05 +0100 Subject: [PATCH 3/5] feat: externalize pagination configuration and add PaginationProperties --- .../BiblioNode/BiblioNodeApplication.java | 3 +- .../BiblioNode/config/PaginationConfig.java | 45 ++++++++++++ .../config/PaginationProperties.java | 9 +++ src/main/resources/application-dev.yaml | 5 ++ src/main/resources/application.yaml | 3 + .../config/PaginationIntegrationTest.java | 69 +++++++++++++++++++ src/test/resources/application-test.yaml | 3 + 7 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/github/mgrablo/BiblioNode/config/PaginationConfig.java create mode 100644 src/main/java/io/github/mgrablo/BiblioNode/config/PaginationProperties.java create mode 100644 src/test/java/io/github/mgrablo/BiblioNode/config/PaginationIntegrationTest.java diff --git a/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java b/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java index 826dc0e..064bd30 100644 --- a/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java +++ b/src/main/java/io/github/mgrablo/BiblioNode/BiblioNodeApplication.java @@ -5,10 +5,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import io.github.mgrablo.BiblioNode.config.LoanProperties; +import io.github.mgrablo.BiblioNode.config.PaginationProperties; import io.github.mgrablo.BiblioNode.config.SecurityProperties; @SpringBootApplication -@EnableConfigurationProperties({LoanProperties.class, SecurityProperties.class}) +@EnableConfigurationProperties({LoanProperties.class, SecurityProperties.class, PaginationProperties.class}) public class BiblioNodeApplication { public static void main(String[] args) { diff --git a/src/main/java/io/github/mgrablo/BiblioNode/config/PaginationConfig.java b/src/main/java/io/github/mgrablo/BiblioNode/config/PaginationConfig.java new file mode 100644 index 0000000..501822d --- /dev/null +++ b/src/main/java/io/github/mgrablo/BiblioNode/config/PaginationConfig.java @@ -0,0 +1,45 @@ +package io.github.mgrablo.BiblioNode.config; + +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class PaginationConfig implements WebMvcConfigurer { + private final PaginationProperties paginationProperties; + + @Override + public void addArgumentResolvers(List resolvers) { + PageableHandlerMethodArgumentResolver pageableResolver = new PageableHandlerMethodArgumentResolver(); + + pageableResolver.setFallbackPageable(PageRequest.of(0, paginationProperties.defaultPageSize())); + pageableResolver.setMaxPageSize(paginationProperties.maxPageSize()); + + resolvers.add(pageableResolver); + } + + @Bean + public OperationCustomizer customizePagination() { + return (operation, handlerMethod) -> { + if (operation.getParameters() != null) { + operation.getParameters().stream() + .filter(p -> "size".equals(p.getName())) + .forEach(p -> { + if (p.getSchema() != null) { + p.getSchema().setDefault(paginationProperties.defaultPageSize()); + } + }); + } + return operation; + }; + } +} diff --git a/src/main/java/io/github/mgrablo/BiblioNode/config/PaginationProperties.java b/src/main/java/io/github/mgrablo/BiblioNode/config/PaginationProperties.java new file mode 100644 index 0000000..e89ebb4 --- /dev/null +++ b/src/main/java/io/github/mgrablo/BiblioNode/config/PaginationProperties.java @@ -0,0 +1,9 @@ +package io.github.mgrablo.BiblioNode.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "app.pagination") +public record PaginationProperties( + int defaultPageSize, + int maxPageSize +) { } diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 43335f9..3fd66a7 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -10,6 +10,9 @@ app: default-loan-days: 21 security: jwt-expiration-hours: 24 + pagination: + default-page-size: 15 + max-page-size: 100 spring: application: @@ -41,6 +44,8 @@ springdoc: logging: level: com.biblionode: DEBUG + org.springdoc: DEBUG + org.springframework.web: DEBUG server: port: 8080 diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 466eb54..1783af0 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -10,6 +10,9 @@ app: default-loan-days: 14 security: jwt-expiration-hours: 1 + pagination: + default-page-size: 20 + max-page-size: 100 spring: application: diff --git a/src/test/java/io/github/mgrablo/BiblioNode/config/PaginationIntegrationTest.java b/src/test/java/io/github/mgrablo/BiblioNode/config/PaginationIntegrationTest.java new file mode 100644 index 0000000..4e0edff --- /dev/null +++ b/src/test/java/io/github/mgrablo/BiblioNode/config/PaginationIntegrationTest.java @@ -0,0 +1,69 @@ +package io.github.mgrablo.BiblioNode.config; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import io.github.mgrablo.BiblioNode.exception.GlobalExceptionHandler; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@Import({SecurityConfiguration.class, GlobalExceptionHandler.class}) +public class PaginationIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private PaginationProperties paginationProperties; + + @MockitoBean + private JwtDecoder jwtDecoder; + @MockitoBean + private JwtEncoder jwtEncoder; + @MockitoBean + private RsaKeyConfig rsaKeyConfig; + + @Test + void shouldApplyDefaultPageSize() throws Exception { + int expectedPageSize = paginationProperties.defaultPageSize(); + mockMvc.perform(get("/api/books") + .with(readerUser()) + ).andExpect(status().isOk()) + .andExpect(jsonPath("$.pageable.pageSize").value(expectedPageSize)) + .andExpect(jsonPath("$.size").value(expectedPageSize)); + } + + @Test + void shouldNotExceedMaxPageSize() throws Exception { + int maxPageSize = paginationProperties.maxPageSize(); + + mockMvc.perform(get("/api/books").with(readerUser()) + .param("size", "10000") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pageable.pageSize").value(maxPageSize)) + .andExpect(jsonPath("$.size").value(maxPageSize)); + } + + + private SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor readerUser() { + return jwt().authorities(new SimpleGrantedAuthority("ROLE_READER")) + .jwt(j -> j.subject("test@email.com")); + } +} diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 90c90df..80d8f40 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -10,6 +10,9 @@ app: default-loan-days: 14 security: jwt-expiration-hours: 24 + pagination: + default-page-size: 7 + max-page-size: 23 spring: datasource: From 4437229725f5df6a7891bc3ac93cbb6837cfd830 Mon Sep 17 00:00:00 2001 From: mgrablo <51509107+Michauu035@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:06:12 +0100 Subject: [PATCH 4/5] feat: update README with configuration details and API documentation access --- README.md | 72 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b5cab37..c18869a 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,10 @@ A library management system built with a layered architecture, focusing on data * [Key Features](#key-features) * [Testing](#testing) * [Setup Instructions](#setup-instructions) + * [Configuration](#configuration) * [Option 1: Quick Run (Docker only)](#option-1-quick-run-docker-only) * [Option 2: Development Mode (Hybrid)](#option-2-development-mode-hybrid) + * [Access API Documentation](#access-api-documentation) * [Cleaning up](#cleaning-up) * [Roadmap](#roadmap) @@ -79,6 +81,7 @@ erDiagram LocalDateTime returnDate } ``` +> **Note:** Entities `Author`, `Book`, `Loan`, `Reader`, and `User` track creation and modification timestamps (`createdAt`, `modifiedAt`) via JPA Auditing. These fields are omitted from the diagram for clarity. ## Key Features - **Authentication & Authorization**: REST API secured with JSON Web Tokens (JWT). Role-based access control (RBAC) separating regular users and administrators. @@ -86,9 +89,9 @@ erDiagram - **JPA Auditing**: Automated tracking of creation and modification timestamps for every resource using `@CreatedDate` and `@LastModifiedDate`. - **Global Exception Handling**: Centralized error management using `@RestControllerAdvice` to ensure consistent JSON error responses across the API. - **Validation**: Input data is strictly validated using Hibernate Validator annotations (e.g., `@NotBlank`, `@Size`) to maintain data quality. -- **Clean Architecture**: Strict separation between database entities and API response models using the DTO (Data Transfer Object) pattern. +- **API-Entity Decoupling**: Strict separation between database entities and API response models (DTOs) to ensure data security and interface stability. - **Database Versioning**: Full schema control and versioning using Liquibase. -- **Optimized Persistence**: Utilization of JPA EntityGraphs to eliminate N+1 query problems during profile aggregation to improve performance by reducing database round-trips.. +- **Optimized Persistence**: Utilization of JPA EntityGraphs to eliminate N+1 query problems during data retrieval, improving performance by reducing database round-trips. - **Externalized Configuration**: Business rules (loan limits, duration) are managed via YAML profiles. ## Testing @@ -96,13 +99,33 @@ The project maintains a high standard of quality through different testing layer - **Unit Tests**: Focused on business logic within the Service layer, utilizing Mockito for dependency isolation. - **Web Layer Tests**: Utilizing MockMvc to verify REST endpoints, HTTP status codes, JSON serialization, and validation logic without starting the full server. - **Persistence Tests**: `@DataJpaTest` used to verify complex JPQL queries and relationship mapping. +- **Code Coverage**: Automated code coverage analysis using **JaCoCo**, with reports generated for every build. - **CI/CD Integration**: Automated test execution via GitHub Actions on every push. ## Setup Instructions -> [!NOTE] -> The application uses `SPRING_PROFILES_ACTIVE` environment variable to determine the active profile. -> Default is `prod`. To verify setup with sample data, use `dev` profile. +### Configuration +The application is configured using environment variables and `application.yaml` properties. + +**Infrastructure & Security** + +| Variable / Property | Description | Default | +|-----------------------------|----------------------------------------|--------------------------------| +| `BD_INITIAL_ADMIN_EMAIL` | Email for the initial admin account | `root@biblionode.com` | +| `BD_INITIAL_ADMIN_PASSWORD` | Password for the initial admin account | `root1234` | +| `RSA_PUBLIC_KEY` | Path to RSA public key for JWT | `file:./certs/public_key.pem` | +| `RSA_PRIVATE_KEY` | Path to RSA private key for JWT | `file:./certs/private_key.pem` | +| `SPRING_PROFILES_ACTIVE` | Active Spring profile (`dev`, `prod`) | `prod` | + +**Business Rules** +Can be adjusted in `application.yaml` or overridden via environment variables (e.g. `app.loan.max-active-loans` -> `APP_LOAN_MAX_ACTIVE_LOANS`). + +| Property Key | Default | Description | +|-------------------------------------|---------|---------------------------------| +| `app.loan.max-active-loans` | `5` | Maximum active loans per reader | +| `app.loan.default-loan-days` | `14` | Loan duration in days | +| `app.security.jwt-expiration-hours` | `1` | JWT token validity (hours) | +| `app.pagination.default-page-size` | `20` | Default page size for lists | ### Option 1: Quick Run (Docker only) Best for quick preview. No Java/Gradle installation required. @@ -112,33 +135,22 @@ Best for quick preview. No Java/Gradle installation required. git clone https://github.com/mgrablo/BiblioNode.git cd BiblioNode ``` -2. **Setup database & Run App**: - You can specify the profile inline: +2. **Start the application**: ```bash - SPRING_PROFILES_ACTIVE=dev docker-compose up -d + # Run with default 'prod' profile + docker-compose up -d ``` - Or just run with default `prod` profile: + *Alternatively, force the 'dev' profile:* ```bash - docker-compose up -d + SPRING_PROFILES_ACTIVE=dev docker-compose up -d ``` - **Default Admin Credentials:** - - Email: `root@biblionode.com` - - Password: `root1234` - - *(You can change these via `BD_INITIAL_ADMIN_EMAIL` and `BD_INITIAL_ADMIN_PASSWORD` environment variables or update them later via API)* - + **Default Credentials**: See [Configuration](#configuration). > [!TIP] - > You can also create a `.env` file in the root directory to set these variables. - > Check `.env.example` for reference. - -3. **Access API documentation**: - - Once the server is running, navigate to: - `http://localhost:8080/swagger-ui/index.html` + > You can also create a `.env` file in the root directory to set variables. Check `.env.example`. ### Option 2: Development Mode (Hybrid) -Best for making changes to the code with fast feedback +Best for making changes to the code with fast feedback. 1. **Start only database**: ```bash @@ -146,10 +158,8 @@ Best for making changes to the code with fast feedback ``` 2. **Start the app locally**: > [!NOTE] - > Make sure you have JDK 21 and Gradle installed locally to run the app in development mode. The app will connect to the database running in Docker. + > Requires JDK 21 and Gradle locally. The app will connect to the database running in Docker. Keys are generated automatically in `certs/` on first run (`dev` profile). - - The app will automatically generate RSA keys in `certs/` folder on first run when the `dev` profile is active. - - Run via IDE or: ```bash # Linux/Mac SPRING_PROFILES_ACTIVE=dev ./gradlew bootRun @@ -157,13 +167,13 @@ Best for making changes to the code with fast feedback # Windows (PowerShell) $env:SPRING_PROFILES_ACTIVE="dev"; ./gradlew bootRun ``` -3. **Access API documentation**: - Once the server is running, navigate to: - `http://localhost:8080/swagger-ui/index.html` +### Access API Documentation +Once the server is running, navigate to: +`http://localhost:8080/swagger-ui.html` ### Cleaning up -If you want to easily reset the database use: +To stop containers and remove volumes (resets database): ```bash docker-compose down -v ``` From 54cc849b279e73a8684141d8aa5c3fca11cc5534 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:16:04 +0000 Subject: [PATCH 5/5] docs: update JaCoCo coverage badge [skip ci] --- .github/badges/jacoco.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index e63c9cd..da988aa 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -1 +1 @@ -coverage99.5% \ No newline at end of file +coverage99% \ No newline at end of file