24 Commits

Author SHA1 Message Date
86df5b9bc7 Merge branch 'dedupe_feature' into development 2026-08-12 13:37:08 +05:30
61bea5fb6e Company options values, portfolio options values migration done 2026-08-12 09:38:18 +05:30
c835a9deb0 Punching, Dedupe and CutOff Features Done 2026-08-12 07:46:24 +05:30
b862a3f686 Fixed Installer Issue 2026-08-08 08:05:29 +05:30
5139f30e86 Merge branch 'edp-punching-feature' into development 2026-08-02 22:39:12 +05:30
e964c74f96 Update .gitignore 2026-08-02 22:38:46 +05:30
bc00515c1b Edit case issues fixed - Now add case and edit case are working fine 2026-08-02 22:32:54 +05:30
60f9aa05a0 Query 19,20,21,22 migrated - Find same case details in punching screen in case of co-applicant 2026-08-02 14:34:21 +05:30
4705649ae8 Auto Cut Feature Done 2026-08-02 12:52:05 +05:30
5acaffc224 Migrated Query - 24 find punchedrecords 2026-08-02 11:49:39 +05:30
0a1b901b12 More refactoring in js code 2026-08-02 11:09:55 +05:30
440c13cc49 Code Refactored 2026-08-02 10:37:20 +05:30
9b9557105c Removed stale files 2026-08-02 01:09:53 +05:30
af360d7793 Case Punching Feature Done 2026-08-02 00:47:00 +05:30
452e6189e4 Migrated Queries - 4,5 and 70 2026-08-01 23:10:28 +05:30
7e4fb66fc6 Restore installer tests excluded by gitignore 2026-08-01 21:56:23 +05:30
213f560c4a Apply .gitignore 2026-08-01 21:44:11 +05:30
421911e41b Merge branch 'query-provider-approach' into development 2026-08-01 16:16:03 +05:30
ebe8e5d574 Update .gitignore 2026-07-27 21:43:04 +05:30
ba820e428e Merge branch 'project-installer-license-workflow' into development 2026-07-26 23:48:21 +05:30
6a91eab764 removed class files 2026-07-26 23:44:20 +05:30
5b8280cbc3 Merge branch 'project-installer-license-workflow' into development 2026-07-26 23:43:30 +05:30
1481c84770 Update .gitignore 2026-07-26 23:42:59 +05:30
a8aa61336f Update .gitignore 2026-07-26 23:39:08 +05:30
250 changed files with 9536 additions and 8816 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,4 @@
/target/
/config/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
@@ -30,7 +29,8 @@
/.nb-gradle/
/cygnus-onprem-app/target
/cygnus-cloud-client/target
/cygnus-installer/src/test
/cygnus-cloud-service/target
/cygnus-installer/src/target
/cygnus-installer/target
/cygnus-onprem-db/target
/cygnus-lib/target

3
.vscode/launch.json vendored
View File

@@ -61,6 +61,7 @@
"REDIS_PASSWORD": "M@triXR3d1s@6202",
"REDIS_DATABASE": "1",
"REDIS_SSL": "false",
"CYGNUS_QUERY_CACHE_ENABLED": "false",
"CYGNUS_CLOUD_BASE_URL": "http://localhost:8090",
"CYGNUS_TOKEN_URL": "http://localhost:8090/oauth2/token",
"CYGNUS_CLIENT_ID": "matrix",
@@ -68,6 +69,8 @@
"CYGNUS_CLIENT_ASSERTION": "file:${workspaceFolder}/config/clients/matrix/matrix-matrix-delhi-cygnus-01-assertion.jwt",
"CYGNUS_LOGIN_KEY_ID": "cygnus-login-2026-01",
"CYGNUS_LOGIN_PUBLIC_KEY": "file:${workspaceFolder}/config/keys/login-public.pem",
"CYGNUS_PAYLOAD_PRIVATE_KEY": "file:${workspaceFolder}/config/keys/case-save-private.pem",
"CYGNUS_PAYLOAD_PUBLIC_KEY": "file:${workspaceFolder}/config/keys/case-save-public.pem",
"CYGNUS_CLOUD_REQUEST_TIMEOUT": "PT10S",
"CYGNUS_TOKEN_REFRESH_SKEW": "PT30S"
},

View File

@@ -0,0 +1 @@
eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.rf8bViJPb5bRJHFamVoCftE0TLA1Gz7JyGnfcjtKEx3KiUL4SSbgYSUQlsKhCt8VJMWE--UpgbkM0Bv3pFVsMF_uh693MhFstEy_A6S3MODjoUV_bMLa_Zr1yH2Jwzud4LSyw-ctJ7G-vO8S_I6YML0HV5kNbobdFmMfXFLGKssVmM_Km0m57ZgSjj3zRii0cKpmdwwSbWoJC2y519TpKhIja0OZAoN1oB3IVKvERlFYCQYUpQmcRkzULGrlSoLIm9iqbRX4qMGhGISwVS6JdVtn9WjpSjF3xkUxN_QsVuYKYFL_kEKWSNepIcfoip7Ag9Qw_S1RZA_KO908MgrzGRbs2UIpxY9sAM8yZ-k1NTk0swV836s5pdDIP3PHovcd6iR8mqWBPt7mq7ES98vNeCZxqPLB_b1JuNfKsbLPdRIvEk2DTjZHeEi-yA1yl5uMi_1f4la6mgy65u_jLL_Ow28uZtj-8yQ3dR4lF3K7tkxQaB4LQiH5nl6lXEndpoPe.Cpu6VoHf2ptCue_L.uH-Pq1WZN-x_c71Iyx8X1QJa1JZRp9sYta9iUOmQ4W8x4Qh2wYm-_tpr8FEw56MLvIb8IH_gHVVLAJgXTmCZhyo26vJlPCtnMMPYFDrPRzTJv9Ewg7RMpsAh_kXFFK4UJ6Tgt_r7Tg_bnVOTKXodUjl3wVdMowZRX_ua-gKwLU-2LDWdqIRmuFDSOkMb1sFBZLPyjWHg_rThs-RwVa4YJdDi74s-L0WX0xRPXzeovu1-xvOgZ0xYf0ScbsFQgvK-JwAxOWsB1WKeHRqnnx5-apK6qtzhpiJ30c7ukPlrg2nieuJvkXOVuKBKXLVRLSViOH4HU76DD96btDUA0Cg2kEURpepucX0VWPyB7K3xb9v1AT42TUkW9wZzdmIs9saRo-ItuWzlCezvapLIsbqmYCs_rW2J3KgqXrUyy2mptn32sqaqQZnlNVu4wwboPCCrbl2MG5CYlYKrwaOHNg611j8sCDkIptm_UmPFnOnzvQYwURuDSgzswTIV5bTd_58yxag2yWwiMdGLMDbxxSUPQ6a4EbuDkqwmGBTSLiG7SaSFchSg6o9EflqK6k-u_rlixCxmfg1A6sa7nloW17dOxDzGKtmn9lBoaskL97nNrhP6ptftak7j4TIZulz1FUOSVoR8kXK8RNQMj-b6rg_MdSO9Ecq5hZsza1SzjkzKZvV-tpP6Wl6-rszCH6g3DIQAWN5aWdUkwuya1HqDDyVPbUOVQPsp4nhVC24QmnlbcqFwQdoCrikYKyX3Snc0H6_K4z9c6nURAdj6THwHmA9BfHtJniI_rEIvkmBk3yndi-H5eAjtxIoYSFQrSTWEFhgvk6bAKoR_xb_cHgzKOxYLBiO_FifkjkFDOlkVBuYnZEp3oRePDKgMDfL5Oxjuie4u-8yrH52Cd32yQzbvGplaXLLeEDf3i71dcz4LkO5DknScWG3G_SbKBpjlse-GKe3XYqRXeRIYLpyiX1MXph0b-Q.dvHU75ZR8wJFxoSacfkthw

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
M/D4ZjXsfrW//BMF6+w2Bh4=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAloJtGMOLcTNWe1IgdrT8
uYucI1Vs02WjyUPZ7Po51iC67QmGdG0InzlBJYdkYtTz8eda+qArZMNGB0ChFs/7
xKPKASkMvVcrm4IYbth0DEIkXWAL7YXsWd8qdS+KsnxuC6TqvuA9Xq/PX4RmtAq4
bl0/1T2HZjJs53/b73l5NehcseZU129w+5lBqDwW5xh+1VnXNbc96za7BFfQSnfg
9WwaWSpo8Y57XATnV7s46yT8gNKM29INHJXs4aIrQ9tcLnTbDz9nhGceNgec3AhO
zc4TyCPugUiG9WD4ehsQr+7Cp30RqT5l9ZPvoBTpAdHLZ1r/jB+qY6+WkY6EsOJW
2xg9bUYoHXjMiFdi8OLXC4NTAPUquFOaIMfjxGaYQc8NoDMFiq3qdTWi2ndCOoxX
yFxYRP5pYs/oA46UAHZsCHaspjvl9jfR/abCALBtsFLwpLA9qqBws5ojXFH//UoX
E7dXnJziCFQNyJOhXtIhbQhtb3wJc6WSLtDrRpFtaJudAgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
m/lDtrY4EpMy9FSQD1zb+qE=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAvyfuM67QxEonEvC2Vf21
P+fxo406Z4C+zNzxH/8tCPhMH94Sklt10WXqSfQFfZbhJPVYoZL8m0NDI7ozI7lI
MPAhAVfcFOzO6VqpFGrzMgMqmnWLrxRsL3y1rjB3LnE84W+ATcS/mlk6rwbCfrSo
RRlY3i79ol8h36jhhaHT2/iGGO3uvqAQK334lQEOlqr+YL4Kkr/GMOm86bE4PcZS
JgCTAnlCQC7TUu6jGWuocmI1nNwLXPjQvXgc+HKtJEqFGf+a0fty9fLgaR2yIzlM
9i31r3kjeKzqVe2DeLTbttd8bVWU4/KmGg8rEchtwC8wR4inuDUjwJ3FwmdBrvDp
Ga1AM8yofdbs73Lv2EgkvRLvlgaWiX2P6KdlKGHeaDj9v0YWJbiFM4rc9y5hD3lG
2unhpvP/49OFXVBT2/pm86LmxSlwsun3G2LKNOXI646jhdVHQkIVcBiHTHYQOe9/
xLqx3ZiEM7witbzuJnxIkbj/8OFgH+WDUmTQVXtbz7vdAgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
RqsZZ1cLu0TCOoNBkcSr79a6
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAtZc750+cxwDcG/lvpEHm
3fxFgp5X5df90s51R4ShKpV6dmDRYXnVil9GmXrJf/aGAqrsf6ZUvZWJrAJ2OO2X
Wt8tcqajnaWdM33BrH/QztTJkoGHZM1xLY5EpRaWo1LaG2gNGsaxG0ON9T1emKi7
XDEcI9Lmc/mhP6SuuUnvGwq1Z433vqGodorkU+T2jCfflhwfenvBcOk4GJZoJFlY
sjy9Dtlha70MJpdVFWffxs4UBhhkjckb7GbDTVxa3xvz1NEbkY2+oh/tJmuIuupA
7A6TnfYP1YPXPMRQFtSPaKMOeKW5J5IorvgQKr4C74HR9iBojJN2oJ6/BjfAaz7P
8kywLj2uo9gJemOm1wz8HZyZsz482GHa0wqpUcFfOIr0oTMiJtvLn/FSYVb5xvNg
3xWiGPMju32Z49XTX7VxTDsIjGx5wtDd4G8ILS3yUlKw0yufHacmy8Fn+Plk+Xtz
Bbz6rq4frV93/3mfVMPGgBDx8mLjVJovPBnXKBSX+SdbAgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
5PO2cnRk24OsUqAswTBT2qjd
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAzpOwECKVW0KdZwlVJtW7
tkqqa1wY1J+HhuFf/kbjmJv8fwFcGj+uyy2s5cXzgckNHz3Cv4Ki8VnQakbn83KR
TNIuMvYgwnaNY35KvVKU5R8KPJ7wV8yEUyhRyjtauaY9TaVUH0MStD3izcZ7Pfej
XJvxRtLjQ9SX/K+joHs3mkWyWDNYm9u6ecLBoPxW6BpAXiUOWnSpc0IO2njGbvnY
I7zs5tYdFjVtDTf4TOSIDXeCXc7nNUhf/yQVAEKvGmr64SihZc+11T5AXJy1eVEj
Fwj7hWqcZxhgukacOoU8oUORO0O0H1HIS8ICXHBgKL17Jq+furEInlU4Pfp1tkA7
dYh9nXQ91+hmRtTSXA1BpzWdIzC3fQqnwTsD6cyIDsPdRB9iSJTywFPbGywMIbh4
wIpUOS3s83DBtpxj2qHw7zc5Q99BP4Dd38kl9ejfdYuFYRmbzrq+BZR7ic5/LM7+
8iExYGpz3sFCr6qJAlCDK4th6TCl2ZjzgpTOF3pnC1I7AgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
VkpSw8QqBfhHSViEh12BQZa2
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA2g0IbNxsNIZ6fKaGTHTQ
pUFfgJVq3x0SZ458xAQDL0l1PZ+lnMtvS3/ZZpbF99bLuqyzUuHwi+1BJ/Nled61
oK8RdJKfUPiQUshXfvWT4/sLsYO6H+v2qw2jBv6kx3h+teDiuHMbw6ikUECr5u1k
qkjb+ls3aijtWplmN25HRhwFB/l6/0xAhEQNA36EoNb+ufsDR/2aRQYGQ6qegynp
8bUt7JDf3hW1y76UCKlKteFfIOkp4zwY0YYq/NewgKlze08LBrX+zy5xr5Vn8nE0
ZtWY8vbNC33Sf6g5D1ZlA+HvquplAWS/rPymj+IN79FeXNegmyCxF/ZafsneaUBn
GpSgXYbyASpA+OkqUqJcBEeTI4rfRZPiyx3ztaibg+rgOEM/DUV5le2WyujnsQ75
IG4SrVu/XTmF08MN8/hD1+ydBVnhVa7bE4WctqgA1T/5BP7ivAB03a1BJHpKFRh/
Q/pO5ONIK8lNGG6aImLPmtJijEvOL0VsUdp+oO87VnTFAgMBAAE=
-----END PUBLIC KEY-----

View File

@@ -1,11 +1,15 @@
package com.cygnus.client;
import com.cygnus.client.model.CloudIdentitySession;
import com.cygnus.client.model.CloudDataItem;
import com.cygnus.client.model.CloudDataRequest;
import com.cygnus.client.model.CloudQueryResponse;
import com.cygnus.client.model.LoginPayload;
import com.cygnus.client.security.LoginEnvelopeEncryptor;
import com.cygnus.client.security.MachineTokenProvider;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -62,4 +66,29 @@ public class CloudIdentityClient {
.bodyToMono(CloudQueryResponse.class))
.timeout(properties.requestTimeout());
}
public Mono<CloudQueryResponse> fetchQuery(String queryKey) {
return tokenProvider.accessToken()
.flatMap(token -> webClient.get()
.uri(properties.baseUri().resolve("/api/v1/queries/key/" + queryKey))
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(CloudQueryResponse.class))
.timeout(properties.requestTimeout());
}
public Mono<List<CloudDataItem>> fetchData(String scope, Map<String, Object> data) {
return tokenProvider.accessToken()
.flatMap(token -> webClient.post()
.uri(properties.baseUri().resolve("/api/v1/platform/data"))
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.bodyValue(new CloudDataRequest(scope, Map.copyOf(data)))
.retrieve()
.bodyToFlux(CloudDataItem.class)
.collectList())
.timeout(properties.requestTimeout());
}
}

View File

@@ -0,0 +1,4 @@
package com.cygnus.client.model;
public record CloudDataItem(Object value, String label, String group) {
}

View File

@@ -0,0 +1,6 @@
package com.cygnus.client.model;
import java.util.Map;
public record CloudDataRequest(String scope, Map<String, Object> data) {
}

View File

@@ -0,0 +1,49 @@
package com.cygnus.cloud.platform.api;
import com.cygnus.cloud.platform.service.ScopedDataException;
import com.cygnus.cloud.platform.service.ScopedDataService;
import jakarta.validation.Valid;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/v1/platform/data")
public class ScopedDataController {
private static final Logger LOGGER = LoggerFactory.getLogger(ScopedDataController.class);
private final ScopedDataService service;
public ScopedDataController(ScopedDataService service) {
this.service = service;
}
@PostMapping
public Flux<ScopedDataItem> data(
@AuthenticationPrincipal Jwt machineJwt,
@Valid @RequestBody ScopedDataRequest request) {
if (machineJwt == null) throw new ScopedDataException("Machine authentication required");
try {
return service.fetch(UUID.fromString(machineJwt.getClaimAsString("tenant_id")), request);
} catch (IllegalArgumentException exception) {
throw new ScopedDataException("Machine tenant is invalid");
}
}
@ExceptionHandler(ScopedDataException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
Map<String, String> invalidRequest(ScopedDataException exception) {
LOGGER.warn("Scoped platform-data request rejected: {}", exception.getMessage());
return Map.of("code", "PLATFORM_DATA_INVALID", "message", exception.getMessage());
}
}

View File

@@ -0,0 +1,7 @@
package com.cygnus.cloud.platform.api;
public record ScopedDataItem(
Object value,
String label,
String group) {
}

View File

@@ -0,0 +1,10 @@
package com.cygnus.cloud.platform.api;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Map;
public record ScopedDataRequest(
@NotBlank String scope,
@NotNull Map<String, Object> data) {
}

View File

@@ -0,0 +1,79 @@
package com.cygnus.cloud.platform.repository;
import com.cygnus.cloud.database.ReactiveDatabaseClient;
import com.cygnus.cloud.platform.api.ScopedDataItem;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
import java.util.List;
import java.util.UUID;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@Repository
public class ScopedDataRepository {
private static final String COMPANY_OPTIONS = """
SELECT ov.opt_value AS value,
ov.val_description AS label,
lower(o.description) || '.' AS group_name
FROM platform.company_options_mapping m
JOIN platform.options o ON o.option_id = m.option_id
JOIN platform.options_values ov ON ov.opt_value_id = m.opt_value_id
JOIN identity.company c ON c.company_id = m.company_id
WHERE c.tenant_id = $1
AND m.company_id = $2
AND o.isactive = 1
AND ov.isactive = 1
AND o.description = ANY($3::text[])
ORDER BY o.description, ov.val_description
""";
private static final String PORTFOLIO_OPTIONS = """
SELECT ov.opt_value AS value,
ov.val_description AS label,
lower(o.description) || '.' AS group_name
FROM platform.options_mapping m
JOIN platform.options o ON o.option_id = m.option_id
JOIN platform.options_values ov ON ov.opt_value_id = m.opt_value_id
JOIN identity.company c ON c.company_id = m.company_id
JOIN platform.company_branch_portfolios cbp
ON cbp.company_id = m.company_id
AND cbp.portfolio_reference = m.portfolio_reference
WHERE c.tenant_id = $1
AND m.company_id = $2
AND cbp.company_branch_id = $3
AND m.portfolio_reference = $4
AND o.isactive = 1
AND ov.isactive = 1
AND o.description = ANY($5::text[])
ORDER BY o.description, ov.val_description
""";
private final ReactiveDatabaseClient database;
public ScopedDataRepository(ReactiveDatabaseClient database) {
this.database = database;
}
public Flux<ScopedDataItem> companyOptions(
UUID tenantId, short companyId, List<String> descriptions) {
return rows(COMPANY_OPTIONS, Tuple.of(tenantId, companyId, descriptions.toArray(String[]::new)));
}
public Flux<ScopedDataItem> portfolioOptions(
UUID tenantId, short companyId, short branchId, UUID portfolioReference,
List<String> descriptions) {
return rows(PORTFOLIO_OPTIONS,
Tuple.of(tenantId, companyId, branchId, portfolioReference,
descriptions.toArray(String[]::new)));
}
private Flux<ScopedDataItem> rows(String sql, Tuple parameters) {
return database.preparedQuery(sql, parameters)
.flatMapMany(result -> Flux.fromIterable(result))
.map(this::item);
}
private ScopedDataItem item(Row row) {
return new ScopedDataItem(
row.getValue("value"), row.getString("label"), row.getString("group_name"));
}
}

View File

@@ -0,0 +1,7 @@
package com.cygnus.cloud.platform.service;
public class ScopedDataException extends RuntimeException {
public ScopedDataException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,74 @@
package com.cygnus.cloud.platform.service;
import com.cygnus.cloud.platform.api.ScopedDataItem;
import com.cygnus.cloud.platform.api.ScopedDataRequest;
import com.cygnus.cloud.platform.repository.ScopedDataRepository;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
@Service
public class ScopedDataService {
public static final String COMPANY_OPTIONS = "company-options";
public static final String PORTFOLIO_OPTIONS = "portfolio-options";
private final ScopedDataRepository repository;
public ScopedDataService(ScopedDataRepository repository) {
this.repository = repository;
}
public Flux<ScopedDataItem> fetch(UUID tenantId, ScopedDataRequest request) {
Map<String, Object> data = request.data();
short companyId = shortValue(data, "companyId");
List<String> descriptions = descriptions(data);
return switch (request.scope().trim().toLowerCase(Locale.ROOT)) {
case COMPANY_OPTIONS -> repository.companyOptions(tenantId, companyId, descriptions);
case PORTFOLIO_OPTIONS -> repository.portfolioOptions(
tenantId, companyId, shortValue(data, "branchId"),
uuidValue(data, "portfolioReference"), descriptions);
default -> Flux.error(new ScopedDataException("Unsupported data scope"));
};
}
private UUID uuidValue(Map<String, Object> data, String name) {
try {
return UUID.fromString(String.valueOf(data.get(name)));
} catch (RuntimeException exception) {
throw new ScopedDataException(name + " must be a valid UUID");
}
}
private short shortValue(Map<String, Object> data, String name) {
Object value = data.get(name);
try {
short result = value instanceof Number number
? number.shortValue() : Short.parseShort(String.valueOf(value));
if (result <= 0) throw new NumberFormatException();
return result;
} catch (RuntimeException exception) {
throw new ScopedDataException(name + " must be a positive number");
}
}
private List<String> descriptions(Map<String, Object> data) {
Object value = data.get("descriptions");
if (!(value instanceof List<?> values)) {
throw new ScopedDataException("descriptions must be a non-empty list");
}
List<String> result = values.stream()
.map(String::valueOf)
.map(String::trim)
.filter(text -> !text.isEmpty())
.map(text -> text.toUpperCase(Locale.ROOT))
.distinct()
.toList();
if (result.isEmpty() || result.size() > 20) {
throw new ScopedDataException("descriptions must contain between 1 and 20 values");
}
return result;
}
}

View File

@@ -25,4 +25,10 @@ public class CloudQueryController {
return repository.findEnabled(queryId)
.switchIfEmpty(Mono.error(new QueryNotFoundException(queryId)));
}
@GetMapping("/key/{queryKey}")
public Mono<CloudQuery> queryByKey(@PathVariable String queryKey) {
return repository.findEnabled(queryKey)
.switchIfEmpty(Mono.error(new QueryNotFoundException(queryKey)));
}
}

View File

@@ -10,8 +10,10 @@ public class QueryCatalogRepository {
private static final String INITIALIZE = """
CREATE SCHEMA IF NOT EXISTS platform;
CREATE SEQUENCE IF NOT EXISTS platform.application_query_id_seq;
CREATE TABLE IF NOT EXISTS platform.application_query (
query_id integer PRIMARY KEY,
query_id integer PRIMARY KEY DEFAULT nextval('platform.application_query_id_seq'),
query_key varchar(100),
query_text text NOT NULL,
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT current_timestamp,
@@ -20,7 +22,15 @@ public class QueryCatalogRepository {
CHECK (query_id > 0),
CONSTRAINT ck_platform_application_query_text
CHECK (length(btrim(query_text)) > 0)
)
);
ALTER TABLE platform.application_query
ADD COLUMN IF NOT EXISTS query_key varchar(100);
ALTER TABLE platform.application_query ALTER COLUMN query_id
SET DEFAULT nextval('platform.application_query_id_seq');
CREATE UNIQUE INDEX IF NOT EXISTS ux_platform_application_query_key
ON platform.application_query (query_key) WHERE query_key IS NOT NULL;
SELECT setval('platform.application_query_id_seq',
greatest(coalesce((SELECT max(query_id) FROM platform.application_query), 0) + 1, 1), false)
""";
private static final String FIND = """
SELECT query_id, query_text
@@ -28,6 +38,12 @@ public class QueryCatalogRepository {
WHERE query_id = $1
AND enabled = true
""";
private static final String FIND_BY_KEY = """
SELECT query_id, query_text
FROM platform.application_query
WHERE query_key = $1
AND enabled = true
""";
private final ReactiveDatabaseClient database;
public QueryCatalogRepository(ReactiveDatabaseClient database) {
@@ -45,4 +61,11 @@ public class QueryCatalogRepository {
.map(row -> new CloudQuery(
row.getInteger("query_id"), row.getString("query_text")));
}
public Mono<CloudQuery> findEnabled(String queryKey) {
return database.preparedQuery(FIND_BY_KEY, Tuple.of(queryKey))
.flatMapMany(rows -> reactor.core.publisher.Flux.fromIterable(rows))
.next()
.map(row -> new CloudQuery(row.getInteger("query_id"), row.getString("query_text")));
}
}

View File

@@ -9,4 +9,8 @@ public class QueryNotFoundException extends RuntimeException {
public QueryNotFoundException(int queryId) {
super("Query was not found: " + queryId);
}
public QueryNotFoundException(String queryKey) {
super("Query was not found: " + queryKey);
}
}

View File

@@ -38,6 +38,8 @@ public class CloudSecurityConfiguration {
.hasAuthority("SCOPE_identity.login")
.pathMatchers("/api/v1/queries/**")
.hasAuthority("SCOPE_identity.login")
.pathMatchers("/api/v1/platform/data")
.hasAuthority("SCOPE_identity.login")
.pathMatchers("/api/v1/admin/**")
.hasAuthority("SCOPE_cygnus.admin")
.anyExchange().authenticated())

View File

@@ -1,7 +1,9 @@
CREATE SCHEMA IF NOT EXISTS platform;
CREATE SEQUENCE IF NOT EXISTS platform.application_query_id_seq;
CREATE TABLE IF NOT EXISTS platform.application_query (
query_id integer PRIMARY KEY,
query_id integer PRIMARY KEY DEFAULT nextval('platform.application_query_id_seq'),
query_key varchar(100),
query_text text NOT NULL,
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT current_timestamp,
@@ -11,3 +13,11 @@ CREATE TABLE IF NOT EXISTS platform.application_query (
CONSTRAINT ck_platform_application_query_text
CHECK (length(btrim(query_text)) > 0)
);
ALTER TABLE platform.application_query
ADD COLUMN IF NOT EXISTS query_key varchar(100);
CREATE UNIQUE INDEX IF NOT EXISTS ux_platform_application_query_key
ON platform.application_query (query_key) WHERE query_key IS NOT NULL;
ALTER TABLE platform.application_query ALTER COLUMN query_id
SET DEFAULT nextval('platform.application_query_id_seq');
SELECT setval('platform.application_query_id_seq',
greatest(coalesce((SELECT max(query_id) FROM platform.application_query), 0) + 1, 1), false);

View File

@@ -0,0 +1,11 @@
-- Stable query key; query_id is generated by PostgreSQL.
BEGIN;
INSERT INTO platform.application_query(query_key, query_text, enabled, created_at, updated_at, ismigrated)
VALUES ('APPLICATION_SAVE',
'procedure!C0L!select * from public.save_application_details(?::jsonb,?::smallint,?::smallint,?::smallint)',
true, clock_timestamp(), clock_timestamp(), true)
ON CONFLICT (query_key) WHERE query_key IS NOT NULL DO UPDATE SET
query_text=excluded.query_text, enabled=true, updated_at=clock_timestamp(), ismigrated=true;
COMMIT;

Binary file not shown.

View File

@@ -42,8 +42,9 @@ import org.springframework.stereotype.Service;
public class SwingInstallationWizard {
private static final String WELCOME = "welcome";
private static final String DATABASE = "database";
private static final String DEPLOYMENT = "deployment";
private static final String DETAILS = "details";
private static final String RUNTIME = "runtime";
private static final String INSTALLING = "installing";
private static final Color ACCENT = new Color(35, 97, 146);
private static final Color BACKGROUND = new Color(236, 243, 247);
@@ -68,7 +69,9 @@ public class SwingInstallationWizard {
private JPasswordField dockerPassword;
private JTextField outputDirectory;
private JTextField containerCloudServiceUrl;
private JTextField databaseUrl;
private JTextField databaseHost;
private JTextField databasePort;
private JTextField databaseName;
private JTextField databaseUsername;
private JPasswordField databasePassword;
private JTextField redisHost;
@@ -139,8 +142,9 @@ public class SwingInstallationWizard {
cardPanel = new JPanel(cards);
cardPanel.setBackground(BACKGROUND);
cardPanel.add(welcomePanel(), WELCOME);
cardPanel.add(databasePanel(), DATABASE);
cardPanel.add(deploymentPanel(), DEPLOYMENT);
cardPanel.add(detailsPanel(), DETAILS);
cardPanel.add(runtimePanel(), RUNTIME);
cardPanel.add(installingPanel(), INSTALLING);
root.add(cardPanel, BorderLayout.CENTER);
return root;
@@ -197,7 +201,8 @@ public class SwingInstallationWizard {
JPanel top = new JPanel(new BorderLayout());
top.setOpaque(false);
top.add(heading("Installation details"), BorderLayout.NORTH);
top.add(text("Enter the activation details supplied by Technobee."), BorderLayout.SOUTH);
top.add(text("Review and enter the activation details supplied by Technobee."),
BorderLayout.SOUTH);
panel.add(top, BorderLayout.NORTH);
JPanel form = new JPanel(new GridBagLayout());
@@ -209,6 +214,32 @@ public class SwingInstallationWizard {
licenseKey = new JPasswordField();
installationCode = new JTextField();
installationName = new JTextField();
int row = 0;
row = addField(form, row, "Client code", clientCode, null);
row = addField(form, row, "License key", licenseKey, null);
row = addField(form, row, "Installation code", installationCode, null);
addField(form, row, "Installation name", installationName, null);
panel.add(form, BorderLayout.CENTER);
JButton back = secondaryButton("Back");
back.addActionListener(event -> cards.show(cardPanel, DEPLOYMENT));
JButton install = primaryButton("Install");
install.addActionListener(event -> startInstallation());
panel.add(actions(back, install), BorderLayout.SOUTH);
return panel;
}
private JPanel deploymentPanel() {
JPanel panel = contentPanel();
panel.setLayout(new BorderLayout(0, 12));
JPanel top = new JPanel(new BorderLayout());
top.setOpaque(false);
top.add(heading("Docker and deployment"), BorderLayout.NORTH);
top.add(text("Configure the container image, registry and deployment location."),
BorderLayout.SOUTH);
panel.add(top, BorderLayout.NORTH);
JPanel form = formPanel();
containerCloudServiceUrl =
new JTextField(settings.containerCloudServiceUrl().toString());
containerCloudServiceUrl.setEditable(true);
@@ -220,10 +251,6 @@ public class SwingInstallationWizard {
outputDirectory = new JTextField(
settings.defaultOutputDirectory().toAbsolutePath().normalize().toString());
int row = 0;
row = addField(form, row, "Client code", clientCode, null);
row = addField(form, row, "License key", licenseKey, null);
row = addField(form, row, "Installation code", installationCode, null);
row = addField(form, row, "Installation name", installationName, null);
row = addField(
form,
row,
@@ -237,8 +264,11 @@ public class SwingInstallationWizard {
dockerImage,
null);
row = addField(form, row, "Docker registry", dockerRegistry, null);
row = addField(form, row, "Docker username", dockerUsername, null);
row = addField(form, row, "Docker password", dockerPassword, null);
row = addComponentField(
form,
row,
"Docker credentials",
credentialFields(dockerUsername, dockerPassword));
for (String field : profile.optionalInstallationFields()) {
JTextField input = new JTextField();
optionalFields.put(field, input);
@@ -254,31 +284,28 @@ public class SwingInstallationWizard {
panel.add(scroll, BorderLayout.CENTER);
JButton back = secondaryButton("Back");
back.addActionListener(event -> cards.show(cardPanel, WELCOME));
back.addActionListener(event -> cards.show(cardPanel, DATABASE));
JButton next = primaryButton("Next");
next.addActionListener(event -> cards.show(cardPanel, RUNTIME));
next.addActionListener(event -> cards.show(cardPanel, DETAILS));
panel.add(actions(back, next), BorderLayout.SOUTH);
return panel;
}
private JPanel runtimePanel() {
private JPanel databasePanel() {
JPanel panel = contentPanel();
panel.setLayout(new BorderLayout(0, 12));
JPanel top = new JPanel(new BorderLayout());
top.setOpaque(false);
top.add(heading("Runtime configuration"), BorderLayout.NORTH);
top.add(heading("Database and Redis"), BorderLayout.NORTH);
top.add(text("Configure the database and Redis services used by the "
+ profile.displayName() + " on-premises container."), BorderLayout.SOUTH);
panel.add(top, BorderLayout.NORTH);
JPanel form = new JPanel(new GridBagLayout());
form.setBackground(PANEL);
form.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(205, 217, 225)),
BorderFactory.createEmptyBorder(14, 16, 14, 16)));
databaseUrl = new JTextField("jdbc:postgresql://localhost:5432/matrix");
JPanel form = formPanel();
databaseHost = new JTextField("localhost");
databasePort = new JTextField("5432");
databaseName = new JTextField("matrix");
databaseUsername = new JTextField("postgres");
databasePassword = new JPasswordField();
redisHost = new JTextField("localhost");
@@ -287,9 +314,16 @@ public class SwingInstallationWizard {
cloudRequestTimeout = new JTextField("PT10S");
tokenRefreshSkew = new JTextField("PT30S");
int row = 0;
row = addField(form, row, "Database URL", databaseUrl, null);
row = addField(form, row, "Database username", databaseUsername, null);
row = addField(form, row, "Database password", databasePassword, null);
row = addComponentField(
form,
row,
"Database",
databaseConnectionFields());
row = addComponentField(
form,
row,
"Database credentials",
credentialFields(databaseUsername, databasePassword));
row = addField(form, row, "Redis host", redisHost, null);
row = addField(form, row, "Redis port", redisPort, null);
row = addField(form, row, "Redis password", redisPassword, null);
@@ -304,10 +338,10 @@ public class SwingInstallationWizard {
panel.add(form, BorderLayout.CENTER);
JButton back = secondaryButton("Back");
back.addActionListener(event -> cards.show(cardPanel, DETAILS));
JButton install = primaryButton("Install");
install.addActionListener(event -> startInstallation());
panel.add(actions(back, install), BorderLayout.SOUTH);
back.addActionListener(event -> cards.show(cardPanel, WELCOME));
JButton next = primaryButton("Next");
next.addActionListener(event -> cards.show(cardPanel, DEPLOYMENT));
panel.add(actions(back, next), BorderLayout.SOUTH);
return panel;
}
@@ -373,7 +407,7 @@ public class SwingInstallationWizard {
progressBar.setValue(0);
progressPercentage.setText("0%");
progressMessage.setText("Preparing installation");
cards.show(cardPanel, RUNTIME);
cards.show(cardPanel, DEPLOYMENT);
containerCloudServiceUrl.requestFocusInWindow();
});
@@ -415,8 +449,8 @@ public class SwingInstallationWizard {
button.setText("Check Docker and continue");
try {
get();
cards.show(cardPanel, DETAILS);
clientCode.requestFocusInWindow();
cards.show(cardPanel, DATABASE);
databaseHost.requestFocusInWindow();
} catch (Exception exception) {
showError(message(exception));
}
@@ -445,7 +479,12 @@ public class SwingInstallationWizard {
optionalValues(),
new RuntimeConfiguration(
containerCloudServiceUrl.getText(),
databaseUrl.getText(),
"jdbc:postgresql://"
+ databaseHost.getText().trim()
+ ":"
+ databasePort.getText().trim()
+ "/"
+ databaseName.getText().trim(),
databaseUsername.getText(),
new String(databaseSecret),
redisHost.getText(),
@@ -559,7 +598,9 @@ public class SwingInstallationWizard {
validateAbsoluteHttpUrl(
runtime.containerCloudServiceUrl(),
"Container cloud service URL");
if (runtime.databaseUrl().isBlank()
if (databaseHost.getText().isBlank()
|| databasePort.getText().isBlank()
|| databaseName.getText().isBlank()
|| runtime.databaseUsername().isBlank()
|| runtime.databasePassword().isBlank()) {
throw new IllegalArgumentException(
@@ -632,6 +673,95 @@ public class SwingInstallationWizard {
return row + 1;
}
private int addComponentField(
JPanel form,
int row,
String label,
Component input) {
GridBagConstraints labelConstraints = constraints(0, row);
labelConstraints.weightx = 0;
labelConstraints.fill = GridBagConstraints.NONE;
labelConstraints.anchor = GridBagConstraints.LINE_START;
JLabel fieldLabel = new JLabel(label);
fieldLabel.setFont(fieldLabel.getFont().deriveFont(Font.BOLD, 12f));
form.add(fieldLabel, labelConstraints);
GridBagConstraints inputConstraints = constraints(1, row);
inputConstraints.weightx = 1;
inputConstraints.fill = GridBagConstraints.HORIZONTAL;
form.add(input, inputConstraints);
return row + 1;
}
private JPanel formPanel() {
JPanel form = new JPanel(new GridBagLayout());
form.setBackground(PANEL);
form.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(205, 217, 225)),
BorderFactory.createEmptyBorder(14, 16, 14, 16)));
return form;
}
private JPanel databaseConnectionFields() {
JPanel fields = inlineFieldsPanel();
addInlineField(fields, databaseHost, 0, 0.50);
addInlineSeparator(fields, ":", 1);
addInlineField(fields, databasePort, 2, 0.15);
addInlineSeparator(fields, "/", 3);
addInlineField(fields, databaseName, 4, 0.35);
databaseHost.setToolTipText("Database host");
databasePort.setToolTipText("Database port");
databaseName.setToolTipText("Database name");
return fields;
}
private JPanel credentialFields(JTextField username, JTextField password) {
JPanel fields = inlineFieldsPanel();
addInlineLabel(fields, "Username", 0);
addInlineField(fields, username, 1, 0.5);
addInlineLabel(fields, "Password", 2);
addInlineField(fields, password, 3, 0.5);
return fields;
}
private JPanel inlineFieldsPanel() {
JPanel fields = new JPanel(new GridBagLayout());
fields.setOpaque(false);
return fields;
}
private void addInlineField(
JPanel panel,
JTextField field,
int column,
double weight) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = column;
constraints.weightx = weight;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.insets = new Insets(0, column == 0 ? 0 : 5, 0, 0);
field.setPreferredSize(new Dimension(120, 29));
panel.add(field, constraints);
}
private void addInlineLabel(JPanel panel, String text, int column) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = column;
constraints.weightx = 0;
constraints.insets = new Insets(0, column == 0 ? 0 : 12, 0, 5);
JLabel label = new JLabel(text);
label.setForeground(new Color(75, 88, 99));
panel.add(label, constraints);
}
private void addInlineSeparator(JPanel panel, String text, int column) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = column;
constraints.weightx = 0;
constraints.insets = new Insets(0, 5, 0, 0);
panel.add(new JLabel(text), constraints);
}
private GridBagConstraints constraints(int column, int row) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridx = column;
@@ -664,6 +794,7 @@ public class SwingInstallationWizard {
JLabel label = new JLabel(value);
label.setFont(label.getFont().deriveFont(Font.BOLD, 19f));
label.setForeground(new Color(35, 48, 58));
label.setAlignmentX(Component.LEFT_ALIGNMENT);
return label;
}
@@ -677,6 +808,7 @@ public class SwingInstallationWizard {
private JPanel summaryRow(String label, String value) {
JPanel row = new JPanel(new BorderLayout(18, 0));
row.setOpaque(false);
row.setAlignmentX(Component.LEFT_ALIGNMENT);
row.setBorder(BorderFactory.createEmptyBorder(7, 0, 7, 0));
JLabel name = new JLabel(label);
name.setPreferredSize(new Dimension(130, 20));
@@ -691,7 +823,8 @@ public class SwingInstallationWizard {
JPanel row = new JPanel(new BorderLayout(18, 0));
row.setOpaque(false);
row.setAlignmentX(Component.LEFT_ALIGNMENT);
row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 34));
row.setBorder(BorderFactory.createEmptyBorder(7, 0, 7, 0));
row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 42));
JLabel name = new JLabel(label);
name.setFont(name.getFont().deriveFont(Font.BOLD));
name.setPreferredSize(new Dimension(130, 28));

View File

@@ -23,6 +23,7 @@ public class TechnobeeServiceInstallerApplication implements CommandLineRunner {
}
public static void main(String[] args) {
disableAccidentalSpringDebug(args);
SpringApplication application =
new SpringApplication(TechnobeeServiceInstallerApplication.class);
application.setWebApplicationType(WebApplicationType.NONE);
@@ -39,6 +40,18 @@ public class TechnobeeServiceInstallerApplication implements CommandLineRunner {
}
}
static void disableAccidentalSpringDebug(String[] args) {
boolean debugRequested = Arrays.stream(args)
.anyMatch(argument -> "--debug".equals(argument)
|| "--debug=true".equalsIgnoreCase(argument));
if (!debugRequested && System.getProperty("debug") == null) {
// DEBUG is commonly exported by developer tools with non-boolean
// values. Spring maps it to its own debug switch unless a more
// specific system property is present.
System.setProperty("debug", "false");
}
}
@Override
public void run(String... args) {
boolean cli = Arrays.asList(args).contains("--cli");

View File

@@ -0,0 +1,29 @@
package com.cygnus.installer;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
class TechnobeeServiceInstallerApplicationTest {
@AfterEach
void clearDebugProperty() {
System.clearProperty("debug");
}
@Test
void disablesDebugUnlessExplicitlyRequested() {
TechnobeeServiceInstallerApplication.disableAccidentalSpringDebug(new String[0]);
assertThat(System.getProperty("debug")).isEqualTo("false");
}
@Test
void preservesExplicitDebugRequest() {
TechnobeeServiceInstallerApplication.disableAccidentalSpringDebug(
new String[] {"--debug"});
assertThat(System.getProperty("debug")).isNull();
}
}

View File

@@ -1,9 +1,7 @@
package lib.constants;
import java.util.Arrays;
/** Canonical user-facing errors shared by Cygnus applications. */
public enum ApplicationError {
/** Canonical user-facing messages shared by Cygnus applications. */
public enum ApplicationMessage {
BAD_REQUEST(400, "HTTP-400", "Invalid request",
"Cygnus could not process the submitted request.",
"Review the supplied information and try again."),
@@ -28,6 +26,37 @@ public enum ApplicationError {
TOO_MANY_REQUESTS(429, "HTTP-429", "Too many requests",
"Cygnus has received too many requests in a short period.",
"Wait briefly before trying again."),
PAYLOAD_INVALID(400, "REQ-4001", "Invalid request details",
"The encrypted request could not be verified.",
"Refresh the page and submit the form again."),
APPLICATION_VALIDATION_FAILED(422, "APP-4221", "Application validation failed",
"One or more application fields contain invalid information.",
"Correct the highlighted fields and submit the application again."),
APPLICATION_ACCESS_DENIED(403, "APP-4031", "Application access denied",
"The selected portfolio or application is not available to your account.",
"Select an authorized portfolio or contact your administrator."),
APPLICATION_SAVE_CONFLICT(409, "APP-4091", "Application save conflict",
"This request was already processed or the application changed.",
"Refresh the application before submitting it again."),
APPLICATION_SAVED(200, "APP-2001", "Application saved",
"The application details were saved successfully.", ""),
APPLICATIONS_LOADED(200, "APP-2002", "Applications loaded",
"The application records were loaded successfully.", ""),
DEDUPE_CHECK_COMPLETED(200, "DDP-2001", "Dedupe check completed",
"The dedupe check completed successfully.", ""),
DEDUPE_CHECK_FAILED(500, "DDP-5001", "Dedupe check failed",
"Cygnus could not complete the dedupe check.",
"Try again. If the issue continues, contact support."),
DEDUPE_DETAILS_LOADED(200, "DDP-2002", "Dedupe details loaded",
"Matching records were loaded successfully.", ""),
DEDUPE_DETAILS_SAVED(200, "DDP-2003", "Dedupe details saved",
"Dedupe remarks were saved successfully.", ""),
APPLICATION_LOAD_FAILED(500, "APP-5002", "Applications could not be loaded",
"Cygnus could not load the application details.",
"Try again. If the issue continues, share the reference ID with support."),
APPLICATION_SAVE_FAILED(500, "APP-5001", "Application could not be saved",
"Cygnus could not save the application details.",
"Try again. If the issue continues, share the reference ID with support."),
INTERNAL_SERVER_ERROR(500, "HTTP-500", "Something went wrong",
"Cygnus could not complete your request.",
"Try again. If the issue continues, share the reference ID with support."),
@@ -47,7 +76,7 @@ public enum ApplicationError {
private final String message;
private final String description;
ApplicationError(
ApplicationMessage(
int httpStatus, String code, String title, String message, String description) {
this.httpStatus = httpStatus;
this.code = code;
@@ -56,11 +85,21 @@ public enum ApplicationError {
this.description = description;
}
public static ApplicationError fromHttpStatus(int status) {
return Arrays.stream(values())
.filter(error -> error.httpStatus == status)
.findFirst()
.orElse(INTERNAL_SERVER_ERROR);
public static ApplicationMessage fromHttpStatus(int status) {
return switch (status) {
case 400 -> BAD_REQUEST;
case 401 -> SESSION_REQUIRED;
case 403 -> ACCESS_DENIED;
case 404 -> PAGE_NOT_FOUND;
case 405 -> METHOD_NOT_ALLOWED;
case 409 -> CONFLICT;
case 413 -> PAYLOAD_TOO_LARGE;
case 429 -> TOO_MANY_REQUESTS;
case 502 -> BAD_GATEWAY;
case 503 -> SERVICE_UNAVAILABLE;
case 504 -> GATEWAY_TIMEOUT;
default -> INTERNAL_SERVER_ERROR;
};
}
public int getHttpStatus() { return httpStatus; }

View File

@@ -0,0 +1,20 @@
package lib.exceptions;
import lib.constants.ApplicationMessage;
/** Single typed application exception carrying a safe user-facing message. */
public class ApplicationException extends RuntimeException {
private final ApplicationMessage applicationMessage;
public ApplicationException(ApplicationMessage message, String technicalMessage) {
super(technicalMessage);
this.applicationMessage = message;
}
public ApplicationException(ApplicationMessage message, String technicalMessage, Throwable cause) {
super(technicalMessage, cause);
this.applicationMessage = message;
}
public ApplicationMessage getApplicationMessage() { return applicationMessage; }
}

View File

@@ -0,0 +1,16 @@
package lib.models;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@Data
public class Allocation {
private List<CutOffRecord> allocationlist = new ArrayList<>();
private String allocTime;
private String errMsg;
private String errDesc;
private String errCode;
private String sessUUID;
private boolean processFlag;
}

View File

@@ -0,0 +1,12 @@
package lib.models;
/** Consistent API response contract for every migrated workflow. */
public record ApiResponse<T>(boolean success, T data, MessageDetails message) {
public static <T> ApiResponse<T> success(T data, MessageDetails message) {
return new ApiResponse<>(true, data, message);
}
public static <T> ApiResponse<T> failure(MessageDetails message) {
return new ApiResponse<>(false, null, message);
}
}

View File

@@ -0,0 +1,87 @@
package lib.models;
import java.io.Serial;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
/** User-editable case initiation data. Identity and audit fields come from UserSession. */
@Getter
@Setter
public class ApplicationDetails implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer applicationId;
private Short portfolioId;
private Short bankBranchId;
private String applicationNumber;
private String bankCode;
private String product;
private String loanAmount;
private String customerName;
private String fatherName;
private String applicationType;
private String category;
private String dateOfBirth;
private String contactPerson;
private String mobileNumber;
private String specialInstruction;
private Boolean residenceVerification;
private Boolean residenceTelephoneVerification;
private Boolean officeVerification;
private Boolean officeTelephoneVerification;
private Boolean propertyVerification;
private Boolean referenceVerification;
private Boolean documentVerification;
private Boolean residenceCoApplicant;
private Boolean residenceCoProprietor;
private Boolean officeCoProprietor;
private Boolean sameResidenceAddress;
private Boolean sameOfficeAddress;
private Boolean samePropertyAddress;
private String residenceAddress1;
private String residenceAddress2;
private String residenceAddress3;
private String residenceLandmark;
private Integer residenceColonyId;
private String residenceCity;
private String residencePincode;
private String residencePhone;
private String companyName;
private String officeAddress1;
private String officeAddress2;
private String officeAddress3;
private String officeLandmark;
private Integer officeColonyId;
private String officeCity;
private String officePincode;
private String department;
private String designation;
private String officePhone;
private String extension;
private String propertyAddress1;
private String propertyAddress2;
private String propertyAddress3;
private String propertyLandmark;
private Integer propertyColonyId;
private String propertyCity;
private String propertyPincode;
private String referenceName1;
private String referenceAddress1;
private String referenceContactNumber1;
private String referenceName2;
private String referenceAddress2;
private String referenceContactNumber2;
private Map<String, String> dynamicFields = new LinkedHashMap<>();
private Boolean autoCutOff;
private Integer formMode;
}

View File

@@ -0,0 +1,4 @@
package lib.models;
/** Data returned after saving a loan-verification application. */
public record ApplicationSaveData(String operation, Integer applicationId, String mvCode) {}

View File

@@ -0,0 +1,16 @@
package lib.models;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/** Result row returned by the application-save database operation. */
@Getter
@Setter
@NoArgsConstructor
public class ApplicationSaveResult {
private String operation;
private Integer applicationId;
private String mvCode;
private String internalUuid;
}

View File

@@ -0,0 +1,13 @@
package lib.models;
import lombok.Data;
@Data
public class CaseGrid {
private String errMsg;
private String errDesc;
private String errCode;
private boolean processFlag;
private String portfolioId;
private String caseID;
}

View File

@@ -9,11 +9,11 @@ import lombok.Setter;
@Getter
@Setter
public class CasePunching {
private ErrorDetails errorDetails;
private MessageDetails messageDetails;
private Map<String, String> optionsl;
private Map<String, List<Option>> options;
private List<String> visibleSections;
private Integer portfolioId;
private Short portfolioId;
private Integer formMode;
private String userId;
private String dynamicFields;