From aabc774cb610d2170e5d15605c60f164680089a9 Mon Sep 17 00:00:00 2001 From: kapiaszczyk <41442206+kapiaszczyk@users.noreply.github.com> Date: Wed, 24 Apr 2024 08:55:39 +0200 Subject: [PATCH] refactor: externalise some configuration --- docker-compose.yml | 29 +++++++++++++------ .../api/data/config/CachingConfig.java | 12 ++++++-- .../api/data/config/SecurityConfig.java | 10 +++---- .../ratelimiting/RateLimitingService.java | 24 ++++++++++----- src/main/resources/application.properties | 10 ++++++- src/test/resources/application.properties | 12 ++++++-- 6 files changed, 70 insertions(+), 27 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 76c8e66..5096806 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,26 @@ version: '2.25.0' -name: jobboard +name: jobboard-stack services: -# api: - # image: kapiaszczyk/jobboard-api:0.0.2 - # ports: - # - "3000:3000" - # depends_on: - # - postgres - # networks: - # - postgres-network + api: + image: kapiaszczyk/job-board:0.1.0 + ports: + - "8080:8080" + depends_on: + - postgres + networks: + - postgres-network + environment: + JDBC_DATABASE_USERNAME: api + JDBC_DATABASE_PASSWORD: apipassword + JDBC_DATABASE_URL: jdbc:postgresql://postgres:5432 + JDBC_DATABASE_NAME: jobboard + RATE_LIMIT_BUCKET_CAPACITY: 10 + RATE_LIMIT_REFILL_PERIOD_SECONDS: 1 + RATE_LIMIT_REFILL_TOKENS: 1 + CACHE_INITIAL_CAPACITY: 100 + CACHE_MAXIMUM_SIZE: 1000 + CACHE_EXPIRE_AFTER_WRITE_MINUTES: 1 postgres: image: postgres:16 environment: diff --git a/src/main/java/com/kapia/jobboard/api/data/config/CachingConfig.java b/src/main/java/com/kapia/jobboard/api/data/config/CachingConfig.java index 87456b3..ff44708 100644 --- a/src/main/java/com/kapia/jobboard/api/data/config/CachingConfig.java +++ b/src/main/java/com/kapia/jobboard/api/data/config/CachingConfig.java @@ -3,6 +3,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.caffeine.CaffeineCacheManager; @@ -17,9 +18,14 @@ public class CachingConfig { private static final Logger LOGGER = LoggerFactory.getLogger(CachingConfig.class); - private static final int INITIAL_CAPACITY = 100; - private static final int MAXIMUM_SIZE = 500; - private static final int EXPIRE_AFTER_WRITE_MINUTES = 1; + @Value("${cache.initial.capacity}") + private int INITIAL_CAPACITY; + + @Value("${cache.maximum.size}") + private int MAXIMUM_SIZE; + + @Value("${cache.expire.after.write.minutes}") + private int EXPIRE_AFTER_WRITE_MINUTES; @Bean public CacheManager cacheManager(Caffeine caffeine) { diff --git a/src/main/java/com/kapia/jobboard/api/data/config/SecurityConfig.java b/src/main/java/com/kapia/jobboard/api/data/config/SecurityConfig.java index 89df9c4..0e3fcdc 100644 --- a/src/main/java/com/kapia/jobboard/api/data/config/SecurityConfig.java +++ b/src/main/java/com/kapia/jobboard/api/data/config/SecurityConfig.java @@ -86,11 +86,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(HttpMethod.POST, "/api/v1/companies/**").hasAnyRole("USER", "ADMIN") .requestMatchers(HttpMethod.PUT, "/api/v1/companies/**").hasAnyRole("USER", "ADMIN") - .requestMatchers(HttpMethod.DELETE, "/api/v1/job-offers/**").hasRole("ADMIN") - .requestMatchers(HttpMethod.DELETE, "/api/v1/companies/**").hasRole("ADMIN") - .requestMatchers(HttpMethod.POST, "/api/v1/technologies").hasRole("ADMIN") - .requestMatchers(HttpMethod.PUT, "/api/v1/technologies/**").hasRole("ADMIN") - .requestMatchers(HttpMethod.GET, "/api/v1/actuator/**").hasRole("ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/job-offers/**").hasAnyRole("ADMIN", "SUPER_ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/v1/companies/**").hasAnyRole("ADMIN", "SUPER_ADMIN") + .requestMatchers(HttpMethod.POST, "/api/v1/technologies").hasAnyRole("ADMIN", "SUPER_ADMIN") + .requestMatchers(HttpMethod.PUT, "/api/v1/technologies/**").hasAnyRole("ADMIN", "SUPER_ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/actuator/**").hasAnyRole("ADMIN", "SUPER_ADMIN") .anyRequest().authenticated() ) diff --git a/src/main/java/com/kapia/jobboard/api/data/ratelimiting/RateLimitingService.java b/src/main/java/com/kapia/jobboard/api/data/ratelimiting/RateLimitingService.java index 45d9dca..b2e397f 100644 --- a/src/main/java/com/kapia/jobboard/api/data/ratelimiting/RateLimitingService.java +++ b/src/main/java/com/kapia/jobboard/api/data/ratelimiting/RateLimitingService.java @@ -4,8 +4,10 @@ import com.kapia.jobboard.api.data.constants.Defaults; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; +import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.time.Duration; @@ -17,14 +19,24 @@ public class RateLimitingService { private static final Logger LOGGER = LoggerFactory.getLogger(AuthController.class); - private static final int BUCKET_CAPACITY = 100; - private static final int REFILL_TOKENS = 100; - private static final Duration REFILL_PERIOD = Duration.ofSeconds(60); + @Value("${rate.limit.bucket.capacity}") + private int BUCKET_CAPACITY; + + @Value("${rate.limit.refill.period.seconds}") + private int REFILL_PERIOD_SECONDS; + + @Value("${rate.limit.refill.tokens}") + private int REFILL_TOKENS; + + private Duration REFILL_PERIOD; + private final Map cache = new ConcurrentHashMap<>(); - public RateLimitingService() { + @PostConstruct + public void init() { + LOGGER.info("Global rate limit: {}, refilling with {} tokens per {} seconds", BUCKET_CAPACITY, REFILL_TOKENS, REFILL_PERIOD_SECONDS); - LOGGER.info("Global rate limit: {}, refilling with {} tokens per {} seconds", BUCKET_CAPACITY, REFILL_TOKENS, REFILL_PERIOD.getSeconds()); + REFILL_PERIOD = Duration.ofSeconds(REFILL_PERIOD_SECONDS); Bandwidth bandwidth = Bandwidth.builder() .capacity(BUCKET_CAPACITY).refillGreedy(REFILL_TOKENS, REFILL_PERIOD).build(); @@ -32,10 +44,8 @@ public RateLimitingService() { Bucket bucket = Bucket.builder().addLimit(bandwidth).build(); cache.put(Defaults.GLOBAL_BUCKET_KEY, bucket); - } - public boolean tryConsume(String key) { Bucket bucket = cache.computeIfAbsent(key, k -> cache.get(Defaults.GLOBAL_BUCKET_KEY)); return bucket.tryConsume(1); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3bc071d..22bbdff 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=jobboard.api ### Spring Data configuration -spring.datasource.url=${JDBC_DATABASE_URL:jdbc:postgresql://localhost:5432/jobboard} +spring.datasource.url=${JDBC_DATABASE_URL:jdbc:postgresql://localhost:5432}/${JDBC_DATABASE_NAME:jobboard} spring.datasource.username=${JDBC_DATABASE_USERNAME:api} spring.datasource.password=${JDBC_DATABASE_PASSWORD:apipassword} spring.datasource.driver-class-name=org.postgresql.Driver @@ -10,6 +10,14 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect management.endpoints.web.exposure.include=index,info,health,metrics,env,caches jwt.private.key:classpath:app.key jwt.public.key:classpath:app.pub +### Rate limiting configuration +rate.limit.bucket.capacity=${RATE_LIMIT_BUCKET_CAPACITY:100} +rate.limit.refill.period.seconds=${RATE_LIMIT_REFILL_PERIOD_SECONDS:60} +rate.limit.refill.tokens=${RATE_LIMIT_REFILL_TOKENS:100} +cache.initial.capacity=${CACHE_INITIAL_CAPACITY:100} +cache.maximum.size=${CACHE_MAXIMUM_SIZE:500} +cache.expire.after.write.minutes=${CACHE_EXPIRE_AFTER_WRITE_MINUTES:1} +### OpenAPI configuration api.title=Job Board API api.version=1.0.0 api.contact=Example Company diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 351d15a..a854349 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,16 +1,24 @@ spring.application.name=jobboard.api ### Spring Data configuration -spring.datasource.url=${JDBC_DATABASE_URL:jdbc:postgresql://localhost:5432/jobboard} +spring.datasource.url=${JDBC_DATABASE_URL:jdbc:postgresql://localhost:5432}/${JDBC_DATABASE_NAME:jobboard} spring.datasource.username=${JDBC_DATABASE_USERNAME:api} spring.datasource.password=${JDBC_DATABASE_PASSWORD:apipassword} spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.hibernate.ddl-auto=update spring.jpa.open-in-view=false spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.ddl-auto=update ### Spring Security configuration management.endpoints.web.exposure.include=index,info,health,metrics,env,caches jwt.private.key:classpath:app.key jwt.public.key:classpath:app.pub +### Rate limiting configuration +rate.limit.bucket.capacity=${RATE_LIMIT_BUCKET_CAPACITY:100} +rate.limit.refill.period.seconds=${RATE_LIMIT_REFILL_PERIOD_SECONDS:60} +rate.limit.refill.tokens=${RATE_LIMIT_REFILL_TOKENS:100} +cache.initial.capacity=${CACHE_INITIAL_CAPACITY:100} +cache.maximum.size=${CACHE_MAXIMUM_SIZE:500} +cache.expire.after.write.minutes=${CACHE_EXPIRE_AFTER_WRITE_MINUTES:1} +### OpenAPI configuration api.title=Job Board API api.version=1.0.0 api.contact=Example Company