Commit: f0438c2

Commit Details

SHAf0438c2cbc2d838bb66a4d8129dd25b5d48d28ee
Treeb8e53d050248c8b632fa3de86003489acae4670b
Author<f69e50@finnacloud.com> 1766443042 +0300
Committer<f69e50@finnacloud.com> 1766443042 +0300
Message
increment once more
GPG Signature
-----BEGIN PGP SIGNATURE-----

iQJSBAABCAA8FiEEWJb139mJI+vZ81KkoAIVSUsXI0oFAmlJyCIeHHNvcGhpYS5l
cmFzbGFuQGZpbm5hY2xvdWQuY29tAAoJEKACFUlLFyNKtrgP/04FZPSa4Hmx9WpR
gOamzjIXc+1+pBvGQDairLZU6rtDSgNkoryrsqOOLIXZWkkxZ70q8/aXQBFThr+t
YyDIz85u9GiwPOV8o1X4sxzF7bupOd6YmOOPqS2vco1aibpB8w9N9EwlMzW95Otw
vnQ/h0kz8MTp0wfXeRJqvLg8DVPnLgW70ly1TZQe19jEA4NwBZOyK2ksTis8iycX
HUM4WW8Velo8O+OtygAbwISr2dILKvkclTLn9kgfpN5esvBvJu4K+xA5T5alDchc
PY3FFeZteQf3GX4v0EH8K3c/q8OcQFMxuRGO31uYGqNbjbOB7zdZVs3N0B1m1efv
vhCgZ6gyxlz0kiozgO6Sx5WFOzHuTlguCK+x0JOtzCokRL/VLiPzmW5Umt280eJz
AlD7u7Dah3khoqDkzXfCL1lxch9tRMVMUYWRMayxC3iculCFM6OCLgzY9hay5y1j
WP1ZOdy/x9jKPoLv5USnw0KlH4rZIHH+aDMDEzWmWUWYDpShylG7sKafXg75bnho
5LbAfCucnDrkjN+rx1dxaKrZswQlIJ8iZ5Q6DzHnDzl4TaFcN1E3exS+xp46H7va
SbR94UfpCyjpTQ2hHdOS7XHINFUDIzWF3mUZ8gqyEANiqpgJ+RcOGX66s69oILpr
vD20SIdMHWDqJH3G6GMNsg1g5/QU
=YrcG
-----END PGP SIGNATURE-----

✓ Verified

File: src/main/java/com/paymentlink/service/RegionService.java

1 package com.paymentlink.service;
2
3 import com.github.benmanes.caffeine.cache.Cache;
4 import com.github.benmanes.caffeine.cache.Caffeine;
5 import com.paymentlink.model.entity.Region;
6 import com.paymentlink.repository.RegionRepository;
7 import jakarta.annotation.PostConstruct;
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10 import org.springframework.beans.factory.annotation.Value;
11 import org.springframework.stereotype.Service;
12 import org.springframework.transaction.annotation.Transactional;
13
14 import java.util.*;
15 import java.util.concurrent.TimeUnit;
16
17 @Service
18 public class RegionService {
19
20 private static final Logger logger = LoggerFactory.getLogger(RegionService.class);
21
22 private final RegionRepository regionRepository;
23 private Cache<String, Region> regionCache;
24 private Cache<String, List<Region>> enabledRegionsCache;
25
26 @Value("${region.cache.ttl:600000}")
27 private long cacheTtl;
28
29 @Value("${region.default.enabled:false}")
30 private boolean defaultEnabled;
31
32 public RegionService(RegionRepository regionRepository) {
33 this.regionRepository = regionRepository;
34 }
35
36 @PostConstruct
37 public void init() {
38 // Initialize local cache (10 min TTL)
39 regionCache = Caffeine.newBuilder()
40 .maximumSize(200)
41 .expireAfterWrite(cacheTtl, TimeUnit.MILLISECONDS)
42 .build();
43
44 enabledRegionsCache = Caffeine.newBuilder()
45 .maximumSize(1)
46 .expireAfterWrite(cacheTtl, TimeUnit.MILLISECONDS)
47 .build();
48
49 logger.info("RegionService initialized with cache TTL: {}ms", cacheTtl);
50 }
51
52 /**
53 * Check if country is enabled
54 */
55 public boolean isCountryEnabled(String countryCode) {
56 Region region = getRegion(countryCode);
57 return region != null && region.getEnabled();
58 }
59
60 /**
61 * Get region by country code
62 */
63 public Region getRegion(String countryCode) {
64 if (countryCode == null || countryCode.isEmpty()) {
65 return null;
66 }
67
68 // Check cache first
69 Region cached = regionCache.getIfPresent(countryCode);
70 if (cached != null) {
71 return cached;
72 }
73
74 // Fetch from database
75 Optional<Region> regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase());
76 if (regionOpt.isPresent()) {
77 Region region = regionOpt.get();
78 regionCache.put(countryCode, region);
79 return region;
80 }
81
82 return null;
83 }
84
85 /**
86 * Get all enabled regions
87 */
88 public List<Region> getAllEnabledRegions() {
89 // Check cache first
90 List<Region> cached = enabledRegionsCache.getIfPresent("enabled");
91 if (cached != null) {
92 return cached;
93 }
94
95 // Fetch from database
96 List<Region> enabled = regionRepository.findAllEnabled();
97 enabledRegionsCache.put("enabled", enabled);
98 return enabled;
99 }
100
101 /**
102 * Get all regions
103 */
104 public List<Region> getAllRegions() {
105 return regionRepository.findAll();
106 }
107
108 /**
109 * Enable a country
110 */
111 @Transactional
112 public void enableCountry(String countryCode) {
113 Optional<Region> regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase());
114 if (regionOpt.isPresent()) {
115 Region region = regionOpt.get();
116 region.setEnabled(true);
117 regionRepository.save(region);
118
119 // Clear caches
120 regionCache.invalidate(countryCode);
121 enabledRegionsCache.invalidateAll();
122
123 logger.info("Enabled country: {}", countryCode);
124 } else {
125 logger.warn("Country not found: {}", countryCode);
126 }
127 }
128
129 /**
130 * Disable a country
131 */
132 @Transactional
133 public void disableCountry(String countryCode) {
134 Optional<Region> regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase());
135 if (regionOpt.isPresent()) {
136 Region region = regionOpt.get();
137 region.setEnabled(false);
138 regionRepository.save(region);
139
140 // Clear caches
141 regionCache.invalidate(countryCode);
142 enabledRegionsCache.invalidateAll();
143
144 logger.info("Disabled country: {}", countryCode);
145 } else {
146 logger.warn("Country not found: {}", countryCode);
147 }
148 }
149
150 /**
151 * Get statistics
152 */
153 public Map<String, Object> getStatistics() {
154 long total = regionRepository.count();
155 long enabled = regionRepository.countEnabled();
156
157 Map<String, Object> stats = new HashMap<>();
158 stats.put("total", total);
159 stats.put("enabled", enabled);
160 stats.put("disabled", total - enabled);
161
162 return stats;
163 }
164
165 /**
166 * Initialize default regions with country data
167 * This method is idempotent and should be called on application startup
168 */
169 @Transactional
170 public void initializeDefaultRegions() {
171 long existingCount = regionRepository.count();
172 if (existingCount > 0) {
173 logger.info("Regions table already populated with {} entries", existingCount);
174 return;
175 }
176
177 logger.info("Initializing default regions...");
178 List<Region> regions = getDefaultRegionData();
179 regionRepository.saveAll(regions);
180 logger.info("Initialized {} regions", regions.size());
181 }
182
183 /**
184 * Get default region data for major countries
185 * In production, this would be loaded from a JSON file or database
186 */
187 private List<Region> getDefaultRegionData() {
188 List<Region> regions = new ArrayList<>();
189
190 // Major regions to enable by default for testing
191 Set<String> enabledByDefault = Set.of("US", "CA", "GB", "FR", "DE", "ES");
192
193 // Add major countries (sample set - in production, load all 195 countries)
194 regions.add(createRegion("US", "United States", "en", "USD", enabledByDefault.contains("US")));
195 regions.add(createRegion("CA", "Canada", "en", "CAD", enabledByDefault.contains("CA")));
196 regions.add(createRegion("GB", "United Kingdom", "en", "GBP", enabledByDefault.contains("GB")));
197 regions.add(createRegion("FR", "France", "fr", "EUR", enabledByDefault.contains("FR")));
198 regions.add(createRegion("DE", "Germany", "de", "EUR", enabledByDefault.contains("DE")));
199 regions.add(createRegion("ES", "Spain", "es", "EUR", enabledByDefault.contains("ES")));
200 regions.add(createRegion("IT", "Italy", "it", "EUR", false));
201 regions.add(createRegion("MX", "Mexico", "es", "MXN", false));
202 regions.add(createRegion("BR", "Brazil", "pt", "BRL", false));
203 regions.add(createRegion("AR", "Argentina", "es", "ARS", false));
204 regions.add(createRegion("JP", "Japan", "ja", "JPY", false));
205 regions.add(createRegion("CN", "China", "zh", "CNY", false));
206 regions.add(createRegion("IN", "India", "en", "INR", false));
207 regions.add(createRegion("AU", "Australia", "en", "AUD", false));
208 regions.add(createRegion("NZ", "New Zealand", "en", "NZD", false));
209 regions.add(createRegion("SG", "Singapore", "en", "SGD", false));
210 regions.add(createRegion("HK", "Hong Kong", "en", "HKD", false));
211 regions.add(createRegion("KR", "South Korea", "ko", "KRW", false));
212 regions.add(createRegion("SE", "Sweden", "sv", "SEK", false));
213 regions.add(createRegion("NO", "Norway", "no", "NOK", false));
214 regions.add(createRegion("DK", "Denmark", "da", "DKK", false));
215 regions.add(createRegion("CH", "Switzerland", "de", "CHF", false));
216 regions.add(createRegion("NL", "Netherlands", "nl", "EUR", false));
217 regions.add(createRegion("BE", "Belgium", "nl", "EUR", false));
218 regions.add(createRegion("AT", "Austria", "de", "EUR", false));
219 regions.add(createRegion("PL", "Poland", "pl", "PLN", false));
220 regions.add(createRegion("PT", "Portugal", "pt", "EUR", false));
221 regions.add(createRegion("IE", "Ireland", "en", "EUR", false));
222 regions.add(createRegion("TR", "Turkey", "tr", "TRY", false));
223 regions.add(createRegion("ZA", "South Africa", "en", "ZAR", false));
224
225 return regions;
226 }
227
228 private Region createRegion(String code, String name, String lang, String currency, boolean enabled) {
229 return Region.builder()
230 .countryCode(code)
231 .countryName(name)
232 .languageCode(lang)
233 .currencyCode(currency)
234 .enabled(enabled)
235 .build();
236 }
237
238 /**
239 * Clear all caches
240 */
241 public void clearCache() {
242 regionCache.invalidateAll();
243 enabledRegionsCache.invalidateAll();
244 logger.info("Region caches cleared");
245 }
246 }
247