Skip to content

Commit fac0aac

Browse files
Merge pull request #576 from pagopa/CHK-4674-massive-iban
feat(iban-bulk): CHK-4674 massive iban
2 parents 4d625e9 + ed1967e commit fac0aac

File tree

17 files changed

+3999
-12
lines changed

17 files changed

+3999
-12
lines changed

openapi/openapi.json

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7174,6 +7174,155 @@
71747174
]
71757175
}
71767176
},
7177+
"/creditor-institutions/{ci-code}/ibans/bulk": {
7178+
"parameters": [
7179+
{
7180+
"description": "This header identifies the call, if not passed it is self-generated. This ID is returned in the response.",
7181+
"in": "header",
7182+
"name": "X-Request-Id",
7183+
"schema": {
7184+
"type": "string"
7185+
}
7186+
}
7187+
],
7188+
"post": {
7189+
"description": "Internal | External | Synchronous | Authorization | Authentication | TPS | Idempotency | Stateless | Read/Write Intense | Cacheable\n-|-|-|-|-|-|-|-|-|-\nY | N | Y | JWT | JWT | 1.0/sec | Y | Y | | N\n",
7190+
"operationId": "bulkIbanOperations",
7191+
"parameters": [
7192+
{
7193+
"description": "Creditor institution code",
7194+
"in": "path",
7195+
"name": "ci-code",
7196+
"required": true,
7197+
"schema": {
7198+
"type": "string"
7199+
}
7200+
}
7201+
],
7202+
"requestBody": {
7203+
"content": {
7204+
"application/json": {
7205+
"schema": {
7206+
"$ref": "#/components/schemas/IbanBulkOperationRequest"
7207+
}
7208+
}
7209+
},
7210+
"required": true
7211+
},
7212+
"responses": {
7213+
"201": {
7214+
"description": "Created - Bulk operations completed successfully",
7215+
"headers": {
7216+
"X-Request-Id": {
7217+
"description": "This header identifies the call",
7218+
"schema": {
7219+
"type": "string"
7220+
}
7221+
}
7222+
}
7223+
},
7224+
"400": {
7225+
"content": {
7226+
"application/json": {
7227+
"schema": {
7228+
"$ref": "#/components/schemas/ProblemJson"
7229+
}
7230+
}
7231+
},
7232+
"description": "Bad Request - Invalid data format, missing required fields",
7233+
"headers": {
7234+
"X-Request-Id": {
7235+
"description": "This header identifies the call",
7236+
"schema": {
7237+
"type": "string"
7238+
}
7239+
}
7240+
}
7241+
},
7242+
"401": {
7243+
"content": {
7244+
"application/json": {
7245+
"schema": {
7246+
"$ref": "#/components/schemas/ProblemJson"
7247+
}
7248+
}
7249+
},
7250+
"description": "Unauthorized - Authentication credentials missing or invalid",
7251+
"headers": {
7252+
"X-Request-Id": {
7253+
"description": "This header identifies the call",
7254+
"schema": {
7255+
"type": "string"
7256+
}
7257+
}
7258+
}
7259+
},
7260+
"403": {
7261+
"content": {
7262+
"application/json": {
7263+
"schema": {
7264+
"$ref": "#/components/schemas/ProblemJson"
7265+
}
7266+
}
7267+
},
7268+
"description": "Forbidden - User does not have permission to perform this operation",
7269+
"headers": {
7270+
"X-Request-Id": {
7271+
"description": "This header identifies the call",
7272+
"schema": {
7273+
"type": "string"
7274+
}
7275+
}
7276+
}
7277+
},
7278+
"404": {
7279+
"content": {
7280+
"application/json": {
7281+
"schema": {
7282+
"$ref": "#/components/schemas/ProblemJson"
7283+
}
7284+
}
7285+
},
7286+
"description": "Not Found - Creditor institution code not found",
7287+
"headers": {
7288+
"X-Request-Id": {
7289+
"description": "This header identifies the call",
7290+
"schema": {
7291+
"type": "string"
7292+
}
7293+
}
7294+
}
7295+
},
7296+
"409": {
7297+
"content": {
7298+
"application/json": {
7299+
"schema": {
7300+
"$ref": "#/components/schemas/ProblemJson"
7301+
}
7302+
}
7303+
},
7304+
"description": "Conflict - Creditor institution already associated with IBAN",
7305+
"headers": {
7306+
"X-Request-Id": {
7307+
"description": "This header identifies the call",
7308+
"schema": {
7309+
"type": "string"
7310+
}
7311+
}
7312+
}
7313+
}
7314+
},
7315+
"security": [
7316+
{
7317+
"JWT": []
7318+
}
7319+
],
7320+
"summary": "Bulk IBAN operations (create, update, delete). Operations are atomic: all or nothing",
7321+
"tags": [
7322+
"Ibans"
7323+
]
7324+
}
7325+
},
71777326
"/creditor-institutions/{ci-code}/ibans/{iban-value}": {
71787327
"delete": {
71797328
"description": "Internal | External | Synchronous | Authorization | Authentication | TPS | Idempotency | Stateless | Read/Write Intense | Cacheable\n-|-|-|-|-|-|-|-|-|-\nY | N | Y | JWT | JWT | 1.0/sec | Y | Y | | N\n",
@@ -14823,6 +14972,22 @@
1482314972
}
1482414973
}
1482514974
},
14975+
"IbanBulkOperationRequest": {
14976+
"required": [
14977+
"operations"
14978+
],
14979+
"type": "object",
14980+
"properties": {
14981+
"operations": {
14982+
"maxItems": 500,
14983+
"minItems": 1,
14984+
"type": "array",
14985+
"items": {
14986+
"$ref": "#/components/schemas/IbanOperation"
14987+
}
14988+
}
14989+
}
14990+
},
1482614991
"IbanCreate": {
1482714992
"required": [
1482814993
"due_date",
@@ -14922,6 +15087,33 @@
1492215087
},
1492315088
"description": "The labels array associated with the IBAN"
1492415089
},
15090+
"IbanOperation": {
15091+
"required": [
15092+
"ibanValue",
15093+
"operation",
15094+
"validityDate"
15095+
],
15096+
"type": "object",
15097+
"properties": {
15098+
"description": {
15099+
"type": "string"
15100+
},
15101+
"ibanValue": {
15102+
"type": "string"
15103+
},
15104+
"operation": {
15105+
"type": "string",
15106+
"enum": [
15107+
"CREATE",
15108+
"UPDATE",
15109+
"DELETE"
15110+
]
15111+
},
15112+
"validityDate": {
15113+
"type": "string"
15114+
}
15115+
}
15116+
},
1492515117
"Ibans": {
1492615118
"required": [
1492715119
"ibans_enhanced"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package it.pagopa.selfcare.pagopa.backoffice.audit;
2+
3+
import org.slf4j.Logger;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
public class AuditLogger {
8+
9+
public void info(Logger logger, String format, Object... args) {
10+
try (var audit = AuditScope.enable()) {
11+
logger.info(format, args);
12+
}
13+
}
14+
15+
public void warn(Logger logger, String format, Object... args) {
16+
try (var audit = AuditScope.enable()) {
17+
logger.warn(format, args);
18+
}
19+
}
20+
21+
public void error(Logger logger, String format, Object... args) {
22+
try (var audit = AuditScope.enable()) {
23+
logger.error(format, args);
24+
}
25+
}
26+
27+
public void error(Logger logger, String message, Throwable throwable) {
28+
try (var audit = AuditScope.enable()) {
29+
logger.error(message, throwable);
30+
}
31+
}
32+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package it.pagopa.selfcare.pagopa.backoffice.audit;
2+
3+
import org.slf4j.MDC;
4+
5+
public final class AuditScope implements AutoCloseable {
6+
private static final String MDC_AUDIT_KEY = "audit";
7+
private static final String MDC_AUDIT_VALUE = "true";
8+
9+
private static final ThreadLocal<AuditState> AUDIT_STATE =
10+
ThreadLocal.withInitial(AuditState::new);
11+
12+
private final AuditState state;
13+
private final boolean isOwner;
14+
15+
private AuditScope(AuditState state, boolean isOwner) {
16+
this.state = state;
17+
this.isOwner = isOwner;
18+
}
19+
20+
public static AuditScope enable() {
21+
AuditState state = AUDIT_STATE.get();
22+
23+
if (state.depth++ == 0) {
24+
MDC.put(MDC_AUDIT_KEY, MDC_AUDIT_VALUE);
25+
return new AuditScope(state, true);
26+
}
27+
28+
return new AuditScope(state, false);
29+
}
30+
31+
@Override
32+
public void close() {
33+
if (--state.depth == 0) {
34+
if (isOwner) {
35+
MDC.remove(MDC_AUDIT_KEY);
36+
}
37+
AUDIT_STATE.remove();
38+
}
39+
}
40+
41+
private static final class AuditState {
42+
int depth = 0;
43+
}
44+
}

src/main/java/it/pagopa/selfcare/pagopa/backoffice/client/ApiConfigClient.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,8 @@
3535
import org.springframework.http.MediaType;
3636
import org.springframework.retry.annotation.Backoff;
3737
import org.springframework.retry.annotation.Retryable;
38-
import org.springframework.web.bind.annotation.DeleteMapping;
39-
import org.springframework.web.bind.annotation.GetMapping;
40-
import org.springframework.web.bind.annotation.PathVariable;
41-
import org.springframework.web.bind.annotation.PostMapping;
42-
import org.springframework.web.bind.annotation.PutMapping;
43-
import org.springframework.web.bind.annotation.RequestBody;
44-
import org.springframework.web.bind.annotation.RequestParam;
38+
import org.springframework.web.bind.annotation.*;
39+
import org.springframework.web.multipart.MultipartFile;
4540

4641
import javax.validation.Valid;
4742
import javax.validation.constraints.Min;
@@ -305,6 +300,12 @@ void deleteCreditorInstitutionIbans(
305300
@PathVariable("ibanValue") String ibanValue
306301
);
307302

303+
@PostMapping(value = "/creditorinstitutions/ibans/csv",
304+
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
305+
produces = MediaType.APPLICATION_JSON_VALUE)
306+
@Valid
307+
void createCreditorInstitutionIbansBulk(@RequestPart("file") MultipartFile file);
308+
308309
@GetMapping(value = "/brokers", produces = MediaType.APPLICATION_JSON_VALUE)
309310
@RequestLine("getBrokersEC")
310311
@Retryable(

src/main/java/it/pagopa/selfcare/pagopa/backoffice/controller/IbanController.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.swagger.v3.oas.annotations.tags.Tag;
1111
import it.pagopa.selfcare.pagopa.backoffice.model.ProblemJson;
1212
import it.pagopa.selfcare.pagopa.backoffice.model.iban.Iban;
13+
import it.pagopa.selfcare.pagopa.backoffice.model.iban.IbanBulkOperationRequest;
1314
import it.pagopa.selfcare.pagopa.backoffice.model.iban.IbanCreate;
1415
import it.pagopa.selfcare.pagopa.backoffice.model.iban.Ibans;
1516
import it.pagopa.selfcare.pagopa.backoffice.security.JwtSecurity;
@@ -21,6 +22,7 @@
2122
import org.springframework.http.MediaType;
2223
import org.springframework.web.bind.annotation.*;
2324

25+
import javax.validation.Valid;
2426
import javax.validation.constraints.NotNull;
2527

2628
import static it.pagopa.selfcare.pagopa.backoffice.model.institutions.ProductRole.ADMIN;
@@ -87,4 +89,22 @@ public void deleteCreditorInstitutionIbans(@Parameter(description = "Creditor in
8789
ibanService.deleteIban(ciCode, ibanValue);
8890
}
8991

92+
@PostMapping(value = "/bulk", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
93+
@ApiResponses(value = {
94+
@ApiResponse(responseCode = "201", description = "Created - Bulk operations completed successfully"),
95+
@ApiResponse(responseCode = "400", description = "Bad Request - Invalid data format, missing required fields", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class))),
96+
@ApiResponse(responseCode = "401", description = "Unauthorized - Authentication credentials missing or invalid", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class))),
97+
@ApiResponse(responseCode = "403", description = "Forbidden - User does not have permission to perform this operation", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class))),
98+
@ApiResponse(responseCode = "404", description = "Not Found - Creditor institution code not found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class))),
99+
@ApiResponse(responseCode = "409", description = "Conflict - Creditor institution already associated with IBAN", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ProblemJson.class)))
100+
})
101+
@ResponseStatus(HttpStatus.CREATED)
102+
@Operation(summary = "Bulk IBAN operations (create, update, delete). Operations are atomic: all or nothing", security = {@SecurityRequirement(name = "JWT")})
103+
@OpenApiTableMetadata
104+
@JwtSecurity(paramName = "ciCode", allowedProductRole = ADMIN)
105+
public void bulkIbanOperations(
106+
@Parameter(description = "Creditor institution code") @PathVariable("ci-code") String ciCode,
107+
@RequestBody @Valid IbanBulkOperationRequest request) {
108+
ibanService.processBulkIbanOperations(ciCode, request.getOperations());
109+
}
90110
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package it.pagopa.selfcare.pagopa.backoffice.model.iban;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
9+
import javax.validation.Valid;
10+
import javax.validation.constraints.NotEmpty;
11+
import javax.validation.constraints.NotNull;
12+
import javax.validation.constraints.Size;
13+
import java.util.List;
14+
15+
@Data
16+
@Builder
17+
@NoArgsConstructor
18+
@AllArgsConstructor
19+
public class IbanBulkOperationRequest {
20+
21+
@JsonProperty("operations")
22+
@NotNull(message = "Operations list cannot be null")
23+
@NotEmpty(message = "Operations list cannot be empty")
24+
@Size(min = 1, max = 500, message = "At least 1 and maximum 500 operations allowed per request")
25+
@Valid
26+
private List<IbanOperation> operations;
27+
}

0 commit comments

Comments
 (0)