Commit: 4617f76

Commit Details

SHA4617f76246bf039920879ae5497436d31494ed7c
Treee78bcb155880b8c167af124554a6db724c41264e
Author<f69e50@finnacloud.com> 1766442953 +0300
Committer<f69e50@finnacloud.com> 1766442953 +0300
Message
rename branch from main to master oops
GPG Signature
-----BEGIN PGP SIGNATURE-----

iQJSBAABCAA8FiEEWJb139mJI+vZ81KkoAIVSUsXI0oFAmlJx8keHHNvcGhpYS5l
cmFzbGFuQGZpbm5hY2xvdWQuY29tAAoJEKACFUlLFyNK2BQP/3EfkXtKsmQoqa4E
e9jdO5BKWUnHwF31PdpxExXwoogkZg2hSjSc0jB9htAKSbQspx+Pst7f9yj2Gf2u
ENGTEQHqqVeLEve2IPc1YJ+F+yedI3NE2PCZJ0+rh1/S14vQTT0kWMSqs6d8Te4K
x4hiTNNjfWidOXQ1vHMXl9iUnevmnko8XqNe3aBZ3JUSRLhhCvehsiPwSjfKGqB8
Sm9i1Y/HaTizFfl4WG5f6MppDgzV2I7Bm/c6K1oDIviO/Wken5vk4TXgLUWDfHJ5
d9m9gh4N9unX4Ivf5G22JVRzxPgox0Y0yFQwpj4IqQ9LzjT2Vz2s+hXoV6HZkfUW
BCLc/6oRmImcSflxOgV/TGaZrvysQ0pz32H8lyLoOI1QAS3o4u1icPFjfHlc5298
zpjGAqPjlVEx4Sjghrow4pb7sb/OyFihTwfjxlLgVfR4tXfb/l5rRt2f9vsm1HJB
qc9F/Qqc6xpb9ECyYZtuuCMToS0MRNkqy2KifrvOaKi2EznhcJu2VcLmjQJookFY
meevIkRenUvjwbs+aoDOa76HmycWkw1NtTtE9M+NSZ9SCBOD+Gp1rqO66z758bUQ
NmISh50GPDiZw5AKDrtsygWRZumLbrWHBleHm935V5O3gYkEKo9kb40Zf2sDsvlS
/wXQCTVz3XuSby3pyaMGgd3ol6gk
=YOD1
-----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