diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70fee9316..8d676f997 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,8 +32,7 @@ jobs: distribution: 'corretto' - run: chmod +x gradlew - run: chmod +x .shell/publish_gen_to_maven_local.sh - - run: | - ./gradlew test + - run: ./gradlew test --info - name: Cache SonarCloud packages uses: actions/cache@v4.2.2 diff --git a/doc/api.yml b/doc/api.yml index b25a45858..4577d822f 100644 --- a/doc/api.yml +++ b/doc/api.yml @@ -377,6 +377,13 @@ paths: $ref: './operations/cor-api.yml#/operations/commentCorById' '/students/{student_id}/cors': $ref: './operations/cor-api.yml#/operations/studentsCors' + # Documenso + /documenso-templates/sync: + $ref: './operations/documenso-api.yml#/operations/syncDocumensoTemplates' + /documenso-documents: + $ref: './operations/documenso-api.yml#/operations/generateDocumensoDocument' + '/documenso-documents/{id}/signing-token': + $ref: './operations/documenso-api.yml#/operations/getDocumensoDocumentSigningToken' components: securitySchemes: diff --git a/doc/components.yml b/doc/components.yml index 753014df0..371bbf217 100644 --- a/doc/components.yml +++ b/doc/components.yml @@ -2324,4 +2324,58 @@ components: amount: type: integer movement: - $ref: '#/components/schemas/CreditMovement' \ No newline at end of file + $ref: '#/components/schemas/CreditMovement' + TemplateDocumenso: + type: object + properties: + id: + type: string + documensoTemplateId: + type: integer + format: int64 + title: + type: string + type: + type: string + adminId: + type: string + DocumensoDocumentStatus: + type: string + enum: + - PENDING + - COMPLETED + - REJECTED + DocumensoDocument: + type: object + properties: + id: + type: string + documensoDocumentId: + type: integer + format: int64 + status: + $ref: '#/components/schemas/DocumensoDocumentStatus' + studentId: + type: string + level: + $ref: '#/components/schemas/StudentLevel' + templateId: + type: string + CrupdateDocumensoDocument: + type: object + properties: + studentId: + type: string + templateName: + type: string + description: >- + Name of the Documenso template to use (e.g. "Fiche d'engagement"), matched against the + template's title as synced from the Documenso interface. + required: + - studentId + - templateName + DocumensoSigningToken: + type: object + properties: + token: + type: string \ No newline at end of file diff --git a/doc/documenso-client-api.yaml b/doc/documenso-client-api.yaml new file mode 100644 index 000000000..9693f5e92 --- /dev/null +++ b/doc/documenso-client-api.yaml @@ -0,0 +1,321 @@ +openapi: 3.0.1 +info: + title: Documenso v2 API (client subset) + description: >- + Minimal subset of the Documenso v2 API (see "Documenso v2 API.yaml" for + the full upstream spec) covering only the operations used by + hei-admin-api's generated Documenso client: browsing templates, creating + a document from a template, and downloading the signed result. + version: 1.0.0 +servers: + - url: https://app.documenso.com/api/v2 +paths: + /template: + get: + operationId: template-findTemplates + summary: Find templates + tags: + - Template + security: + - apiKey: [] + parameters: + - in: query + name: query + schema: + type: string + - in: query + name: page + schema: + type: number + - in: query + name: perPage + schema: + type: number + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + type: object + properties: + id: + type: number + title: + type: string + type: + type: string + enum: + - PUBLIC + - PRIVATE + - ORGANISATION + userId: + type: number + createdAt: + type: string + updatedAt: + type: string + required: + - id + - title + - userId + required: + - data + /template/{templateId}: + get: + operationId: template-getTemplateById + summary: Get template + tags: + - Template + security: + - apiKey: [] + parameters: + - in: path + name: templateId + required: true + schema: + type: number + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: number + title: + type: string + userId: + type: number + recipients: + type: array + items: + type: object + properties: + id: + type: number + role: + type: string + enum: + - CC + - SIGNER + - VIEWER + - APPROVER + - ASSISTANT + email: + type: string + name: + type: string + required: + - id + - role + fields: + type: array + items: + type: object + properties: + id: + type: number + type: + type: string + label: + type: string + placeholder: + type: string + page: + type: number + positionY: + type: number + required: + - id + - type + required: + - id + - title + - recipients + /template/use: + post: + operationId: template-createDocumentFromTemplate + summary: Use template + description: Use the template to create a document + tags: + - Template + security: + - apiKey: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + templateId: + type: number + recipients: + type: array + items: + type: object + properties: + id: + type: number + email: + type: string + name: + type: string + required: + - id + - email + prefillFields: + type: array + items: + type: object + properties: + id: + type: number + type: + type: string + enum: + - text + value: + type: string + required: + - id + - type + - value + required: + - templateId + - recipients + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: number + status: + type: string + enum: + - DRAFT + - PENDING + - COMPLETED + - REJECTED + title: + type: string + createdAt: + type: string + recipients: + type: array + items: + type: object + properties: + id: + type: number + email: + type: string + name: + type: string + role: + type: string + enum: + - CC + - SIGNER + - VIEWER + - APPROVER + - ASSISTANT + token: + type: string + required: + - id + - status + - recipients + /document/{documentId}: + get: + operationId: document-get + summary: Get document + tags: + - Document + security: + - apiKey: [] + parameters: + - in: path + name: documentId + required: true + schema: + type: number + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: number + status: + type: string + enum: + - DRAFT + - PENDING + - COMPLETED + - REJECTED + title: + type: string + createdAt: + type: string + completedAt: + type: string + required: + - id + - status + /document/{documentId}/download: + get: + operationId: document-download + summary: Download document + description: >- + Downloads the document. "signed" returns the completed document with + signatures, "original" returns the original uploaded document. + tags: + - Document + security: + - apiKey: [] + parameters: + - in: path + name: documentId + required: true + schema: + type: number + - in: query + name: version + schema: + type: string + enum: + - original + - signed + default: signed + responses: + "200": + description: Successful response + content: + application/pdf: + schema: + type: string + format: binary +components: + securitySchemes: + apiKey: + type: apiKey + in: header + name: Authorization +security: + - apiKey: [] diff --git a/doc/operations/documenso-api.yml b/doc/operations/documenso-api.yml new file mode 100644 index 000000000..d7eff4f2b --- /dev/null +++ b/doc/operations/documenso-api.yml @@ -0,0 +1,72 @@ +operations: + syncDocumensoTemplates: + post: + tags: + - Documenso + summary: Sync the local template catalog from the templates created in the Documenso interface + operationId: syncDocumensoTemplates + responses: + '200': + description: The synced templates + content: + application/json: + schema: + type: array + items: + $ref: '../components.yml#/components/schemas/TemplateDocumenso' + '403': + $ref: '../components.yml#/components/responses/403' + '500': + $ref: '../components.yml#/components/responses/500' + generateDocumensoDocument: + post: + tags: + - Documenso + summary: Generate a document from a Documenso template for a promotion level, to be signed by the admin and the monitor + operationId: generateDocumensoDocument + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components.yml#/components/schemas/CrupdateDocumensoDocument' + responses: + '200': + description: The pending Documenso document + content: + application/json: + schema: + $ref: '../components.yml#/components/schemas/DocumensoDocument' + '400': + $ref: '../components.yml#/components/responses/400' + '403': + $ref: '../components.yml#/components/responses/403' + '404': + $ref: '../components.yml#/components/responses/404' + '500': + $ref: '../components.yml#/components/responses/500' + getDocumensoDocumentSigningToken: + get: + tags: + - Documenso + summary: Get the Documenso signing token for the authenticated user's recipient slot on a document + operationId: getDocumensoDocumentSigningToken + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: The signing token + content: + application/json: + schema: + $ref: '../components.yml#/components/schemas/DocumensoSigningToken' + '403': + $ref: '../components.yml#/components/responses/403' + '404': + $ref: '../components.yml#/components/responses/404' + '500': + $ref: '../components.yml#/components/responses/500' \ No newline at end of file diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/DocumensoDocumentController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/DocumensoDocumentController.java new file mode 100644 index 000000000..3d9873e49 --- /dev/null +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/DocumensoDocumentController.java @@ -0,0 +1,38 @@ +package school.hei.haapi.endpoint.rest.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import school.hei.haapi.endpoint.rest.mapper.DocumensoMapper; +import school.hei.haapi.endpoint.rest.model.CrupdateDocumensoDocument; +import school.hei.haapi.endpoint.rest.model.DocumensoDocument; +import school.hei.haapi.endpoint.rest.model.DocumensoSigningToken; +import school.hei.haapi.endpoint.rest.security.model.Principal; +import school.hei.haapi.service.DocumensoDocumentService; + +@RestController +@RequiredArgsConstructor +public class DocumensoDocumentController { + private final DocumensoDocumentService documensoDocumentService; + private final DocumensoMapper documensoMapper; + + @PostMapping("/documenso-documents") + public DocumensoDocument generateDocumensoDocument( + @RequestBody CrupdateDocumensoDocument toCreate) { + var document = + documensoDocumentService.generateDocument( + toCreate.getStudentId(), toCreate.getTemplateName()); + return documensoMapper.toRest(document); + } + + @GetMapping("/documenso-documents/{id}/signing-token") + public DocumensoSigningToken getDocumensoDocumentSigningToken( + @PathVariable("id") String id, @AuthenticationPrincipal Principal principal) { + var token = documensoDocumentService.getSigningToken(id, principal.getUserId()); + return new DocumensoSigningToken().token(token); + } +} diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/DocumensoWebhookController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/DocumensoWebhookController.java new file mode 100644 index 000000000..1e9d82c0f --- /dev/null +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/DocumensoWebhookController.java @@ -0,0 +1,35 @@ +package school.hei.haapi.endpoint.rest.controller; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import school.hei.haapi.service.DocumensoDocumentService; +import school.hei.haapi.service.documenso.DocumensoWebhookPayload; + +@RestController +public class DocumensoWebhookController { + private final DocumensoDocumentService documensoDocumentService; + private final String webhookSecret; + + public DocumensoWebhookController( + DocumensoDocumentService documensoDocumentService, + @Value("${documenso.webhook.secret}") String webhookSecret) { + this.documensoDocumentService = documensoDocumentService; + this.webhookSecret = webhookSecret; + } + + @PostMapping("/documenso/webhook") + public ResponseEntity receiveDocumensoWebhook( + @RequestHeader(value = "X-Documenso-Secret", required = false) String secret, + @RequestBody DocumensoWebhookPayload payload) { + if (!webhookSecret.equals(secret)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + documensoDocumentService.handleWebhook(payload); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/TemplateDocumensoController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/TemplateDocumensoController.java new file mode 100644 index 000000000..09356ace0 --- /dev/null +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/TemplateDocumensoController.java @@ -0,0 +1,21 @@ +package school.hei.haapi.endpoint.rest.controller; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import school.hei.haapi.endpoint.rest.mapper.DocumensoMapper; +import school.hei.haapi.endpoint.rest.model.TemplateDocumenso; +import school.hei.haapi.service.TemplateDocumensoService; + +@RestController +@RequiredArgsConstructor +public class TemplateDocumensoController { + private final TemplateDocumensoService templateDocumensoService; + private final DocumensoMapper documensoMapper; + + @PostMapping("/documenso-templates/sync") + public List syncDocumensoTemplates() { + return templateDocumensoService.syncTemplates().stream().map(documensoMapper::toRest).toList(); + } +} diff --git a/src/main/java/school/hei/haapi/endpoint/rest/mapper/DocumensoMapper.java b/src/main/java/school/hei/haapi/endpoint/rest/mapper/DocumensoMapper.java new file mode 100644 index 000000000..9e9b7c68b --- /dev/null +++ b/src/main/java/school/hei/haapi/endpoint/rest/mapper/DocumensoMapper.java @@ -0,0 +1,28 @@ +package school.hei.haapi.endpoint.rest.mapper; + +import org.springframework.stereotype.Component; +import school.hei.haapi.endpoint.rest.model.DocumensoDocument; +import school.hei.haapi.endpoint.rest.model.DocumensoDocumentStatus; +import school.hei.haapi.endpoint.rest.model.TemplateDocumenso; + +@Component +public class DocumensoMapper { + public TemplateDocumenso toRest(school.hei.haapi.model.TemplateDocumenso domain) { + return new TemplateDocumenso() + .id(domain.getId()) + .documensoTemplateId(domain.getDocumensoTemplateId()) + .title(domain.getTitle()) + .type(domain.getType()) + .adminId(domain.getAdmin() == null ? null : domain.getAdmin().getId()); + } + + public DocumensoDocument toRest(school.hei.haapi.model.DocumensoDocument domain) { + return new DocumensoDocument() + .id(domain.getId()) + .documensoDocumentId(domain.getDocumensoDocumentId()) + .status(DocumensoDocumentStatus.valueOf(domain.getStatus().name())) + .studentId(domain.getStudent().getId()) + .level(domain.getLevel()) + .templateId(domain.getTemplate().getId()); + } +} diff --git a/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java b/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java index 0532e2877..e18c1a606 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java @@ -51,7 +51,6 @@ public class SecurityConf { public SecurityConf( CasdoorAuthProvider authProvider, - // InternalToExternalErrorHandler behind @Qualifier("handlerExceptionResolver") HandlerExceptionResolver exceptionResolver, CourseAssignmentService courseAssignmentService, MonitoringStudentService monitoringStudentService, @@ -152,6 +151,9 @@ req, res, null, forbiddenWithRemoteInfo(req)))) antMatcher(POST, "/cors/*/comment"), antMatcher(GET, "/students/*/cors"), antMatcher(PUT, "/students/*/cors"), + antMatcher(POST, "/documenso-templates/sync"), + antMatcher(POST, "/documenso-documents"), + antMatcher(GET, "/documenso-documents/*/signing-token"), antMatcher(PUT, "/students/*/fees/*/mpbs"), antMatcher(GET, "/students/*/fees/*/mpbs"), antMatcher(GET, "/students/*/fees/*/mpbs/verifications"), @@ -350,6 +352,7 @@ req, res, null, forbiddenWithRemoteInfo(req)))) // casdoor new AntPathRequestMatcher("/authentication/signin", POST.name()), new AntPathRequestMatcher("/authentication/login-url", GET.name()), + new AntPathRequestMatcher("/documenso/webhook", POST.name()), new AntPathRequestMatcher("/**", OPTIONS.toString()))) .permitAll() .requestMatchers(GET, "/whoami") @@ -1093,6 +1096,12 @@ req, res, null, forbiddenWithRemoteInfo(req)))) .hasAnyRole(TEACHER.getRole(), MANAGER.getRole(), ADMIN.getRole()) .requestMatchers(PUT, "/students/*/cors") .hasAnyRole(MANAGER.getRole(), ADMIN.getRole()) + .requestMatchers(POST, "/documenso-templates/sync") + .hasAnyRole(ADMIN.getRole()) + .requestMatchers(POST, "/documenso-documents") + .hasAnyRole(ADMIN.getRole(), MANAGER.getRole()) + .requestMatchers(GET, "/documenso-documents/*/signing-token") + .hasAnyRole(ADMIN.getRole(), MANAGER.getRole(), MONITOR.getRole()) // // Attendances resources // diff --git a/src/main/java/school/hei/haapi/model/DocumensoDocument.java b/src/main/java/school/hei/haapi/model/DocumensoDocument.java new file mode 100644 index 000000000..d820c5879 --- /dev/null +++ b/src/main/java/school/hei/haapi/model/DocumensoDocument.java @@ -0,0 +1,65 @@ +package school.hei.haapi.model; + +import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.GenerationType.IDENTITY; +import static org.hibernate.type.SqlTypes.NAMED_ENUM; + +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.io.Serializable; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.JdbcTypeCode; +import school.hei.haapi.endpoint.rest.model.StudentLevel; + +@Table(name = "documenso_document") +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +@EqualsAndHashCode +@ToString +public class DocumensoDocument implements Serializable { + @Id + @GeneratedValue(strategy = IDENTITY) + private String id; + + private Long documensoDocumentId; + + @ManyToOne + @JoinColumn(name = "documenso_template_id") + private TemplateDocumenso template; + + @ManyToOne + @JoinColumn(name = "student_id") + private User student; + + @Enumerated(STRING) + private StudentLevel level; + + @Enumerated(STRING) + @JdbcTypeCode(NAMED_ENUM) + private DocumensoDocumentStatus status; + + @ManyToOne + @JoinColumn(name = "file_info_id") + private FileInfo fileInfo; + + @CreationTimestamp private Instant creationDatetime; + + private Instant completedDatetime; +} diff --git a/src/main/java/school/hei/haapi/model/DocumensoDocumentRecipient.java b/src/main/java/school/hei/haapi/model/DocumensoDocumentRecipient.java new file mode 100644 index 000000000..4d5acee67 --- /dev/null +++ b/src/main/java/school/hei/haapi/model/DocumensoDocumentRecipient.java @@ -0,0 +1,46 @@ +package school.hei.haapi.model; + +import static jakarta.persistence.GenerationType.IDENTITY; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.io.Serializable; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Table(name = "documenso_document_recipient") +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +@EqualsAndHashCode +@ToString +public class DocumensoDocumentRecipient implements Serializable { + @Id + @GeneratedValue(strategy = IDENTITY) + private String id; + + @ManyToOne + @JoinColumn(name = "documenso_document_id") + private DocumensoDocument document; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + private Long documensoRecipientId; + private String signingToken; + private Instant signedDatetime; +} diff --git a/src/main/java/school/hei/haapi/model/DocumensoDocumentStatus.java b/src/main/java/school/hei/haapi/model/DocumensoDocumentStatus.java new file mode 100644 index 000000000..a5faaf0d9 --- /dev/null +++ b/src/main/java/school/hei/haapi/model/DocumensoDocumentStatus.java @@ -0,0 +1,7 @@ +package school.hei.haapi.model; + +public enum DocumensoDocumentStatus { + PENDING, + COMPLETED, + REJECTED +} diff --git a/src/main/java/school/hei/haapi/model/PersonSnapshot.java b/src/main/java/school/hei/haapi/model/PersonSnapshot.java new file mode 100644 index 000000000..aa630e6b9 --- /dev/null +++ b/src/main/java/school/hei/haapi/model/PersonSnapshot.java @@ -0,0 +1,32 @@ +package school.hei.haapi.model; + +public record PersonSnapshot(String fullName, String nic, String address, String phone) { + public PersonSnapshot(User user) { + this( + user.getFirstName() + " " + user.getLastName(), + user.getNic(), + user.getAddress(), + user.getPhone()); + } + + public String getAddressField() { + return address; + } + + public String getPhoneField() { + return phone; + } + + public String getNicField() { + return nic; + } + + public String field(String labelKeyword) { + return switch (labelKeyword) { + case "adresse personnelle" -> address; + case "telephone" -> phone; + case "titulaire de la cin" -> nic; + default -> null; + }; + } +} diff --git a/src/main/java/school/hei/haapi/model/TemplateDocumenso.java b/src/main/java/school/hei/haapi/model/TemplateDocumenso.java new file mode 100644 index 000000000..7c72a344c --- /dev/null +++ b/src/main/java/school/hei/haapi/model/TemplateDocumenso.java @@ -0,0 +1,40 @@ +package school.hei.haapi.model; + +import static jakarta.persistence.GenerationType.IDENTITY; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.io.Serializable; +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.CreationTimestamp; + +@Table(name = "documenso_template") +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +@EqualsAndHashCode +@ToString +public class TemplateDocumenso implements Serializable { + @Id + @GeneratedValue(strategy = IDENTITY) + private String id; + + private Long documensoTemplateId; + private String title; + private String type; + @ManyToOne private User admin; + @CreationTimestamp private Instant creationDatetime; +} diff --git a/src/main/java/school/hei/haapi/model/TemplateFieldLabels.java b/src/main/java/school/hei/haapi/model/TemplateFieldLabels.java new file mode 100644 index 000000000..0276c73ca --- /dev/null +++ b/src/main/java/school/hei/haapi/model/TemplateFieldLabels.java @@ -0,0 +1,16 @@ +package school.hei.haapi.model; + +/** + * Keywords matched against Documenso template field labels. Labels are normalized (accents + * stripped, lowercased) before comparison, so keywords must be written accent-free and lowercase. + */ +public final class TemplateFieldLabels { + public static final String FULL_NAME = "nom et prenom"; + public static final String LEVEL = "niveau"; + public static final String PARENT_INDICATOR = "tuteur"; + public static final String ADDRESS = "adresse personnelle"; + public static final String PHONE = "telephone"; + public static final String NIC = "titulaire de la cin"; + + private TemplateFieldLabels() {} +} diff --git a/src/main/java/school/hei/haapi/model/User.java b/src/main/java/school/hei/haapi/model/User.java index c0bff3429..cd70c0664 100644 --- a/src/main/java/school/hei/haapi/model/User.java +++ b/src/main/java/school/hei/haapi/model/User.java @@ -119,6 +119,8 @@ public class User implements Serializable { private String profilePictureKey; + private Long documensoUserId; + // RELATION (TEACHER): Course Assignment @OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTeacher") @ToString.Exclude diff --git a/src/main/java/school/hei/haapi/repository/DocumensoDocumentRecipientRepository.java b/src/main/java/school/hei/haapi/repository/DocumensoDocumentRecipientRepository.java new file mode 100644 index 000000000..9c093da5a --- /dev/null +++ b/src/main/java/school/hei/haapi/repository/DocumensoDocumentRecipientRepository.java @@ -0,0 +1,13 @@ +package school.hei.haapi.repository; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import school.hei.haapi.model.DocumensoDocumentRecipient; + +@Repository +public interface DocumensoDocumentRecipientRepository + extends JpaRepository { + Optional findByDocument_IdAndUser_Id( + String documentId, String userId); +} diff --git a/src/main/java/school/hei/haapi/repository/DocumensoDocumentRepository.java b/src/main/java/school/hei/haapi/repository/DocumensoDocumentRepository.java new file mode 100644 index 000000000..e54fbdd46 --- /dev/null +++ b/src/main/java/school/hei/haapi/repository/DocumensoDocumentRepository.java @@ -0,0 +1,11 @@ +package school.hei.haapi.repository; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import school.hei.haapi.model.DocumensoDocument; + +@Repository +public interface DocumensoDocumentRepository extends JpaRepository { + Optional findByDocumensoDocumentId(Long documensoDocumentId); +} diff --git a/src/main/java/school/hei/haapi/repository/FeeRepository.java b/src/main/java/school/hei/haapi/repository/FeeRepository.java index 281859dcd..2116bbf46 100644 --- a/src/main/java/school/hei/haapi/repository/FeeRepository.java +++ b/src/main/java/school/hei/haapi/repository/FeeRepository.java @@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import school.hei.haapi.endpoint.rest.model.FeeFrequency; import school.hei.haapi.endpoint.rest.model.FeeStatusEnum; import school.hei.haapi.model.Fee; @@ -18,6 +19,8 @@ public interface FeeRepository extends JpaRepository { List findAllByStatus(FeeStatusEnum status); + List findAllByFrequency(FeeFrequency frequency); + List getFeesByStudentIdAndStatusOrderByDueDatetimeDesc( String studentId, FeeStatusEnum status, Pageable pageable); diff --git a/src/main/java/school/hei/haapi/repository/TemplateDocumensoRepository.java b/src/main/java/school/hei/haapi/repository/TemplateDocumensoRepository.java new file mode 100644 index 000000000..1a9887313 --- /dev/null +++ b/src/main/java/school/hei/haapi/repository/TemplateDocumensoRepository.java @@ -0,0 +1,14 @@ +package school.hei.haapi.repository; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import school.hei.haapi.model.TemplateDocumenso; + +@Repository +public interface TemplateDocumensoRepository extends JpaRepository { + Optional findByDocumensoTemplateId(Long documensoTemplateId); + + List findAllByTitleContainingIgnoreCase(String title); +} diff --git a/src/main/java/school/hei/haapi/repository/UserRepository.java b/src/main/java/school/hei/haapi/repository/UserRepository.java index 97db018f9..a5fa8c0ff 100644 --- a/src/main/java/school/hei/haapi/repository/UserRepository.java +++ b/src/main/java/school/hei/haapi/repository/UserRepository.java @@ -18,6 +18,8 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); + Optional findByDocumensoUserId(Long documensoUserId); + List findAllByStatus(User.Status status); List findAllByRoleAndStatus(Role role, User.Status status); diff --git a/src/main/java/school/hei/haapi/service/AdvancedFeeStatsService.java b/src/main/java/school/hei/haapi/service/AdvancedFeeStatsService.java index 393b492ad..d5688ece2 100644 --- a/src/main/java/school/hei/haapi/service/AdvancedFeeStatsService.java +++ b/src/main/java/school/hei/haapi/service/AdvancedFeeStatsService.java @@ -3,7 +3,6 @@ import static java.time.Instant.now; import static java.time.ZoneOffset.UTC; import static java.time.temporal.ChronoUnit.DAYS; -import static java.time.temporal.ChronoUnit.SECONDS; import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingByConcurrent; @@ -38,6 +37,8 @@ import java.time.Duration; import java.time.Instant; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -175,11 +176,11 @@ public String generateAdvancedFeesStatsExcelFile( sheet.autoSizeColumn(i); } workbook.write(bytes); - var now = Instant.now().truncatedTo(SECONDS); + var now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")); + var filename = "advanced-fees-stats-" + now + ".xlsx"; var file = createFileFromBytes(bytes.toByteArray(), "advanced-fees-stats-" + now, ".xlsx"); - var bucketKey = "advanced-fees-stats-" + now + ".xlsx"; - bucketComponent.upload(file, bucketKey); - return bucketComponent.presign(bucketKey, Duration.ofDays(1)).toString(); + bucketComponent.upload(file, filename); + return bucketComponent.presign(filename, Duration.ofDays(1)).toString(); } catch (IOException e) { throw new ApiException(SERVER_EXCEPTION, e); } diff --git a/src/main/java/school/hei/haapi/service/DocumensoDocumentService.java b/src/main/java/school/hei/haapi/service/DocumensoDocumentService.java new file mode 100644 index 000000000..886e16d27 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/DocumensoDocumentService.java @@ -0,0 +1,172 @@ +package school.hei.haapi.service; + +import static school.hei.haapi.model.exception.ApiException.ExceptionType.SERVER_EXCEPTION; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import school.hei.haapi.endpoint.rest.model.StudentLevel; +import school.hei.haapi.model.DocumensoDocument; +import school.hei.haapi.model.DocumensoDocumentRecipient; +import school.hei.haapi.model.PersonSnapshot; +import school.hei.haapi.model.TemplateDocumenso; +import school.hei.haapi.model.User; +import school.hei.haapi.model.exception.ApiException; +import school.hei.haapi.model.exception.NotFoundException; +import school.hei.haapi.model.promotion.PromotionLevelOutOfRangeException; +import school.hei.haapi.repository.DocumensoDocumentRecipientRepository; +import school.hei.haapi.repository.DocumensoDocumentRepository; +import school.hei.haapi.repository.MonitoringStudentRepository; +import school.hei.haapi.repository.UserRepository; +import school.hei.haapi.service.documenso.DocumensoClient; +import school.hei.haapi.service.documenso.DocumensoTemplateResolver; +import school.hei.haapi.service.documenso.DocumensoWebhookHandler; +import school.hei.haapi.service.documenso.DocumensoWebhookPayload; +import school.hei.haapi.service.documenso.PrefillFieldsFactory; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplate200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequest; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequestRecipientsInner; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200Response; + +@Service +@AllArgsConstructor +public class DocumensoDocumentService { + private final DocumensoClient documensoClient; + private final DocumensoDocumentRepository documensoDocumentRepository; + private final DocumensoDocumentRecipientRepository documensoDocumentRecipientRepository; + private final UserRepository userRepository; + private final MonitoringStudentRepository monitoringStudentRepository; + private final DocumensoTemplateResolver templateResolver; + private final PrefillFieldsFactory prefillFieldsFactory; + private final DocumensoWebhookHandler webhookHandler; + + @Transactional + public DocumensoDocument generateDocument(String studentId, String templateName) { + var student = + userRepository + .findById(studentId) + .orElseThrow(() -> new NotFoundException("User with id: " + studentId)); + var monitor = + monitoringStudentRepository.findAllMonitorsByStudentId(studentId).stream() + .findFirst() + .orElseThrow(() -> new NotFoundException("No monitor linked to student " + studentId)); + var level = safeLevelAt(student); + var template = templateResolver.resolveByName(templateName, level); + + try { + var remoteTemplate = documensoClient.getTemplate(template.getDocumensoTemplateId()); + validateRemoteTemplate(remoteTemplate, template.getDocumensoTemplateId()); + + var request = buildDocumentRequest(remoteTemplate, template, student, monitor, level); + var response = documensoClient.useTemplate(request); + + return persistDocument(template, student, level, monitor, response); + } catch (RestClientException e) { + throw new ApiException(SERVER_EXCEPTION, e); + } + } + + private TemplateCreateDocumentFromTemplateRequest buildDocumentRequest( + TemplateGetTemplateById200Response remoteTemplate, + TemplateDocumenso template, + User student, + User monitor, + StudentLevel level) { + var request = new TemplateCreateDocumentFromTemplateRequest(); + request.setTemplateId(BigDecimal.valueOf(remoteTemplate.getId().longValue())); + request.setRecipients( + List.of(toRecipient(remoteTemplate.getRecipients().getFirst().getId(), monitor))); + request.setPrefillFields( + prefillFieldsFactory.buildPrefillFields( + template, + remoteTemplate.getFields(), + new PersonSnapshot(student), + new PersonSnapshot(monitor), + level)); + return request; + } + + private void validateRemoteTemplate( + TemplateGetTemplateById200Response remoteTemplate, Long documensoTemplateId) { + var placeholders = remoteTemplate.getRecipients(); + if (placeholders == null || placeholders.isEmpty()) { + throw new ApiException( + SERVER_EXCEPTION, + "Documenso template " + documensoTemplateId + " must define a recipient placeholder"); + } + } + + private DocumensoDocument persistDocument( + TemplateDocumenso template, + User student, + StudentLevel level, + User monitor, + TemplateCreateDocumentFromTemplate200Response response) { + var document = + documensoDocumentRepository.save( + DocumensoDocument.builder() + .documensoDocumentId(response.getId().longValue()) + .template(template) + .student(student) + .level(level) + .status(school.hei.haapi.model.DocumensoDocumentStatus.PENDING) + .build()); + + for (var recipient : response.getRecipients()) { + documensoDocumentRecipientRepository.save( + DocumensoDocumentRecipient.builder() + .document(document) + .user(monitor) + .documensoRecipientId(recipient.getId().longValue()) + .signingToken(recipient.getToken()) + .build()); + } + return document; + } + + private TemplateCreateDocumentFromTemplateRequestRecipientsInner toRecipient( + BigDecimal placeholderId, User user) { + var recipient = new TemplateCreateDocumentFromTemplateRequestRecipientsInner(); + recipient.setId(placeholderId); + recipient.setEmail(user.getEmail()); + recipient.setName(user.getFirstName() + " " + user.getLastName()); + return recipient; + } + + private StudentLevel safeLevelAt(User student) { + return student + .findCurrentGroup() + .flatMap( + group -> { + try { + return Optional.of(group.getPromotion().getLevelAt(Instant.now())); + } catch (PromotionLevelOutOfRangeException e) { + return Optional.empty(); + } + }) + .orElse(null); + } + + public String getSigningToken(String documentId, String requestingUserId) { + return documensoDocumentRecipientRepository + .findByDocument_IdAndUser_Id(documentId, requestingUserId) + .map(DocumensoDocumentRecipient::getSigningToken) + .orElseThrow( + () -> + new NotFoundException( + "No Documenso recipient for document " + + documentId + + " and user " + + requestingUserId)); + } + + @Transactional + public void handleWebhook(DocumensoWebhookPayload payload) { + webhookHandler.handle(payload); + } +} \ No newline at end of file diff --git a/src/main/java/school/hei/haapi/service/TemplateDocumensoService.java b/src/main/java/school/hei/haapi/service/TemplateDocumensoService.java new file mode 100644 index 000000000..6a43c1bb9 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/TemplateDocumensoService.java @@ -0,0 +1,42 @@ +package school.hei.haapi.service; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; +import school.hei.haapi.model.TemplateDocumenso; +import school.hei.haapi.repository.TemplateDocumensoRepository; +import school.hei.haapi.repository.UserRepository; +import school.hei.haapi.service.documenso.DocumensoClient; +import school.hei.haapi.service.documenso.gen.model.TemplateFindTemplates200ResponseDataInner; + +@Service +@AllArgsConstructor +public class TemplateDocumensoService { + private final DocumensoClient documensoClient; + private final TemplateDocumensoRepository templateDocumensoRepository; + private final UserRepository userRepository; + + @SneakyThrows + public List syncTemplates() { + var response = documensoClient.findTemplates(null, 1, 100); + return response.getData().stream().map(this::upsert).toList(); + } + + private TemplateDocumenso upsert(TemplateFindTemplates200ResponseDataInner remote) { + var documensoTemplateId = remote.getId().longValue(); + var template = + templateDocumensoRepository + .findByDocumensoTemplateId(documensoTemplateId) + .orElseGet(TemplateDocumenso::new); + template.setDocumensoTemplateId(documensoTemplateId); + template.setTitle(remote.getTitle()); + template.setType(remote.getType() == null ? null : remote.getType().getValue()); + if (remote.getUserId() != null) { + userRepository + .findByDocumensoUserId(remote.getUserId().longValue()) + .ifPresent(template::setAdmin); + } + return templateDocumensoRepository.save(template); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoClient.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoClient.java new file mode 100644 index 000000000..1adf2e03a --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoClient.java @@ -0,0 +1,50 @@ +package school.hei.haapi.service.documenso; + +import java.io.File; +import java.math.BigDecimal; +import org.springframework.web.client.RestClientException; +import school.hei.haapi.service.documenso.gen.api.DocumentApi; +import school.hei.haapi.service.documenso.gen.api.TemplateApi; +import school.hei.haapi.service.documenso.gen.invoker.ApiClient; +import school.hei.haapi.service.documenso.gen.model.DocumentGet200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplate200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequest; +import school.hei.haapi.service.documenso.gen.model.TemplateFindTemplates200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200Response; + +public class DocumensoClient { + private final TemplateApi templateApi; + private final DocumentApi documentApi; + + public DocumensoClient(String baseUrl, String apiKey) { + var apiClient = new ApiClient(); + apiClient.setBasePath(baseUrl); + apiClient.setApiKey(apiKey); + this.templateApi = new TemplateApi(apiClient); + this.documentApi = new DocumentApi(apiClient); + } + + public TemplateFindTemplates200Response findTemplates(String query, int page, int perPage) + throws RestClientException { + return templateApi.templateFindTemplates( + query, BigDecimal.valueOf(page), BigDecimal.valueOf(perPage)); + } + + public TemplateGetTemplateById200Response getTemplate(long templateId) + throws RestClientException { + return templateApi.templateGetTemplateById(BigDecimal.valueOf(templateId)); + } + + public TemplateCreateDocumentFromTemplate200Response useTemplate( + TemplateCreateDocumentFromTemplateRequest request) throws RestClientException { + return templateApi.templateCreateDocumentFromTemplate(request); + } + + public DocumentGet200Response getDocument(long documentId) throws RestClientException { + return documentApi.documentGet(BigDecimal.valueOf(documentId)); + } + + public File downloadSignedDocument(long documentId) throws RestClientException { + return documentApi.documentDownload(BigDecimal.valueOf(documentId), "signed"); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoConf.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoConf.java new file mode 100644 index 000000000..c1df96a4a --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoConf.java @@ -0,0 +1,23 @@ +package school.hei.haapi.service.documenso; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DocumensoConf { + + private final String apiUrl; + private final String apiKey; + + public DocumensoConf( + @Value("${documenso.api.url}") String apiUrl, @Value("${documenso.api.key}") String apiKey) { + this.apiUrl = apiUrl; + this.apiKey = apiKey; + } + + @Bean + public DocumensoClient documensoClient() { + return new DocumensoClient(apiUrl, apiKey); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoDocumentBuilder.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoDocumentBuilder.java new file mode 100644 index 000000000..f22ba6df4 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoDocumentBuilder.java @@ -0,0 +1,13 @@ +package school.hei.haapi.service.documenso; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import school.hei.haapi.repository.DocumensoDocumentRecipientRepository; +import school.hei.haapi.repository.DocumensoDocumentRepository; + +@Component +@AllArgsConstructor +public class DocumensoDocumentBuilder { + private final DocumensoDocumentRepository documensoDocumentRepository; + private final DocumensoDocumentRecipientRepository documensoDocumentRecipientRepository; +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoDocumentEvent.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoDocumentEvent.java new file mode 100644 index 000000000..6ec7f50f6 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoDocumentEvent.java @@ -0,0 +1,16 @@ +package school.hei.haapi.service.documenso; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class DocumensoDocumentEvent { + @NotNull + @JsonProperty("id") + private Long id; +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoTemplateResolver.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoTemplateResolver.java new file mode 100644 index 000000000..354b22b41 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoTemplateResolver.java @@ -0,0 +1,49 @@ +package school.hei.haapi.service.documenso; + +import java.text.Normalizer; +import java.util.Locale; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import school.hei.haapi.endpoint.rest.model.StudentLevel; +import school.hei.haapi.model.TemplateDocumenso; +import school.hei.haapi.model.exception.ApiException; +import school.hei.haapi.model.exception.ApiException.ExceptionType; +import school.hei.haapi.model.exception.NotFoundException; +import school.hei.haapi.repository.TemplateDocumensoRepository; + +@Component +@AllArgsConstructor +public class DocumensoTemplateResolver { + private final TemplateDocumensoRepository templateDocumensoRepository; + + public TemplateDocumenso resolveByName(String templateName, StudentLevel level) { + var candidates = templateDocumensoRepository.findAllByTitleContainingIgnoreCase(templateName); + if (candidates.isEmpty()) { + throw new NotFoundException("No synced Documenso template matching: " + templateName); + } + if (candidates.size() == 1) { + return candidates.getFirst(); + } + if (level != null) { + var matchingLevel = + candidates.stream() + .filter( + candidate -> normalize(candidate.getTitle()).contains(normalize(level.name()))) + .toList(); + if (matchingLevel.size() == 1) { + return matchingLevel.getFirst(); + } + } + throw new ApiException( + ExceptionType.SERVER_EXCEPTION, + "Several Documenso templates match \"" + + templateName + + "\" and the student's level doesn't disambiguate them: " + + candidates.stream().map(TemplateDocumenso::getTitle).toList()); + } + + private static String normalize(String value) { + var withoutAccents = Normalizer.normalize(value, Normalizer.Form.NFD).replaceAll("\\p{M}", ""); + return withoutAccents.toLowerCase(Locale.FRENCH); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoWebhookHandler.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoWebhookHandler.java new file mode 100644 index 000000000..35ff8ea89 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoWebhookHandler.java @@ -0,0 +1,71 @@ +package school.hei.haapi.service.documenso; + +import java.time.Instant; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestClientException; +import school.hei.haapi.service.documenso.DocumensoWebhookPayload; +import school.hei.haapi.endpoint.rest.model.FileType; +import school.hei.haapi.file.bucket.BucketComponent; +import school.hei.haapi.model.DocumensoDocument; +import school.hei.haapi.model.DocumensoDocumentStatus; +import school.hei.haapi.model.FileInfo; +import school.hei.haapi.model.exception.ApiException; +import school.hei.haapi.model.exception.ApiException.ExceptionType; +import school.hei.haapi.model.exception.NotFoundException; +import school.hei.haapi.repository.DocumensoDocumentRepository; +import school.hei.haapi.repository.FileInfoRepository; + +@Component +@AllArgsConstructor +public class DocumensoWebhookHandler { + private final DocumensoClient documensoClient; + private final DocumensoDocumentRepository documensoDocumentRepository; + private final FileInfoRepository fileInfoRepository; + private final BucketComponent bucketComponent; + + @Transactional + public void handle(DocumensoWebhookPayload payload) { + if (!payload.isDocumentCompleted()) { + return; + } + if (payload.getPayload() == null || payload.getPayload().getId() == null) { + return; + } + var documensoDocumentId = payload.getPayload().getId(); + var document = + documensoDocumentRepository + .findByDocumensoDocumentId(documensoDocumentId) + .orElseThrow(() -> new NotFoundException("Documenso document " + documensoDocumentId)); + + try { + downloadAndSaveSignedDocument(document, documensoDocumentId); + markDocumentCompleted(document); + } catch (RestClientException e) { + throw new ApiException(ExceptionType.SERVER_EXCEPTION, e); + } + } + + private void downloadAndSaveSignedDocument(DocumensoDocument document, Long documensoDocumentId) { + var signedFile = documensoClient.downloadSignedDocument(documensoDocumentId); + var bucketKey = "documenso-documents/" + documensoDocumentId + ".pdf"; + bucketComponent.upload(signedFile, bucketKey); + + var fileInfo = + fileInfoRepository.save( + FileInfo.builder() + .name(bucketKey) + .fileType(FileType.OTHER) + .filePath(bucketKey) + .build()); + + document.setFileInfo(fileInfo); + } + + private void markDocumentCompleted(DocumensoDocument document) { + document.setStatus(DocumensoDocumentStatus.COMPLETED); + document.setCompletedDatetime(Instant.now()); + documensoDocumentRepository.save(document); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/DocumensoWebhookPayload.java b/src/main/java/school/hei/haapi/service/documenso/DocumensoWebhookPayload.java new file mode 100644 index 000000000..14f902d44 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/DocumensoWebhookPayload.java @@ -0,0 +1,23 @@ +package school.hei.haapi.service.documenso; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class DocumensoWebhookPayload { + @NotNull + @JsonProperty("event") + private String event; + + @JsonProperty("payload") + private DocumensoDocumentEvent payload; + + public boolean isDocumentCompleted() { + return event != null && event.contains("COMPLETED"); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/PrefillFieldsFactory.java b/src/main/java/school/hei/haapi/service/documenso/PrefillFieldsFactory.java new file mode 100644 index 000000000..8df0baf34 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/PrefillFieldsFactory.java @@ -0,0 +1,168 @@ +package school.hei.haapi.service.documenso; + +import java.math.BigDecimal; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import school.hei.haapi.endpoint.rest.model.StudentLevel; +import school.hei.haapi.model.PersonSnapshot; +import school.hei.haapi.model.TemplateDocumenso; +import school.hei.haapi.model.TemplateFieldLabels; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200ResponseFieldsInner; + +@Component +@AllArgsConstructor +public class PrefillFieldsFactory { + private final DocumensoTemplateResolver templateResolver; + + public List buildPrefillFields( + TemplateDocumenso template, + List fields, + PersonSnapshot student, + PersonSnapshot monitor, + StudentLevel level) { + if (fields == null || fields.isEmpty()) { + return List.of(); + } + var textFields = fields.stream().filter(f -> "TEXT".equalsIgnoreCase(f.getType())).toList(); + if (normalize(template.getTitle()).contains("engagement")) { + return buildFicheEngagementFields(textFields, student, monitor, level); + } + return buildDefaultFields(textFields, student, level); + } + + private List + buildFicheEngagementFields( + List textFields, + PersonSnapshot student, + PersonSnapshot monitor, + StudentLevel level) { + var prefillFields = + new ArrayList(); + + matchOnly(textFields, TemplateFieldLabels.FULL_NAME, student.fullName()) + .ifPresent(prefillFields::add); + matchOnly(textFields, TemplateFieldLabels.LEVEL, level == null ? null : getLevelString(level)) + .ifPresent(prefillFields::add); + matchByPosition(textFields, TemplateFieldLabels.PARENT_INDICATOR, monitor.fullName(), true) + .ifPresent(prefillFields::add); + + for (var label : + List.of(TemplateFieldLabels.ADDRESS, TemplateFieldLabels.PHONE, TemplateFieldLabels.NIC)) { + addFieldPairIfFound(textFields, prefillFields, label, monitor, student); + } + return prefillFields; + } + + private List buildDefaultFields( + List textFields, + PersonSnapshot student, + StudentLevel level) { + var prefillFields = + new ArrayList(); + matchOnly(textFields, TemplateFieldLabels.FULL_NAME, student.fullName()) + .ifPresent(prefillFields::add); + matchOnly(textFields, TemplateFieldLabels.LEVEL, level == null ? null : getLevelString(level)) + .ifPresent(prefillFields::add); + matchOnly(textFields, TemplateFieldLabels.NIC, student.nic()).ifPresent(prefillFields::add); + matchOnly(textFields, TemplateFieldLabels.ADDRESS, student.address()) + .ifPresent(prefillFields::add); + matchOnly(textFields, TemplateFieldLabels.PHONE, student.phone()).ifPresent(prefillFields::add); + return prefillFields; + } + + private void addFieldPairIfFound( + List textFields, + ArrayList prefillFields, + String label, + PersonSnapshot monitor, + PersonSnapshot student) { + var candidates = fieldsMatching(textFields, label); + if (candidates.size() >= 2) { + matchAt(candidates.getFirst(), monitor.field(label)).ifPresent(prefillFields::add); + matchAt(candidates.get(candidates.size() - 1), student.field(label)) + .ifPresent(prefillFields::add); + } else if (candidates.size() == 1) { + matchAt(candidates.getFirst(), student.field(label)).ifPresent(prefillFields::add); + } + } + + private Optional matchOnly( + List fields, + String labelKeyword, + String value) { + if (value == null || value.isBlank()) { + return Optional.empty(); + } + return fields.stream() + .filter(field -> labelContains(field, labelKeyword)) + .findFirst() + .map(field -> toPrefillField(field.getId(), value)); + } + + private Optional matchByPosition( + List fields, + String labelKeyword, + String value, + boolean topmost) { + var candidates = fieldsMatching(fields, labelKeyword); + if (candidates.isEmpty()) { + return Optional.empty(); + } + var chosen = topmost ? candidates.getFirst() : candidates.get(candidates.size() - 1); + return matchAt(chosen, value); + } + + private List fieldsMatching( + List fields, String labelKeyword) { + return fields.stream() + .filter(field -> labelContains(field, labelKeyword)) + .sorted( + Comparator.comparing( + (TemplateGetTemplateById200ResponseFieldsInner f) -> orZero(f.getPage())) + .thenComparing(f -> orZero(f.getPositionY()))) + .toList(); + } + + private Optional matchAt( + TemplateGetTemplateById200ResponseFieldsInner field, String value) { + if (value == null || value.isBlank()) { + return Optional.empty(); + } + return Optional.of(toPrefillField(field.getId(), value)); + } + + private static boolean labelContains( + TemplateGetTemplateById200ResponseFieldsInner field, String labelKeyword) { + var label = field.getLabel() != null ? field.getLabel() : field.getPlaceholder(); + return label != null && normalize(label).contains(labelKeyword); + } + + private static BigDecimal orZero(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value; + } + + private static String normalize(String value) { + var withoutAccents = Normalizer.normalize(value, Normalizer.Form.NFD).replaceAll("\\p{M}", ""); + return withoutAccents.toLowerCase(Locale.FRENCH); + } + + private TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner toPrefillField( + BigDecimal fieldId, String value) { + var field = new TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner(); + field.setId(fieldId); + field.setType(TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.TypeEnum.TEXT); + field.setValue(value); + return field; + } + + private String getLevelString(StudentLevel level) { + return school.hei.haapi.model.Promotion.getLevelString(level); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/api/DocumentApi.java b/src/main/java/school/hei/haapi/service/documenso/gen/api/DocumentApi.java new file mode 100644 index 000000000..37ee87eb6 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/api/DocumentApi.java @@ -0,0 +1,216 @@ +package school.hei.haapi.service.documenso.gen.api; + +import java.io.File; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; +import school.hei.haapi.service.documenso.gen.invoker.ApiClient; +import school.hei.haapi.service.documenso.gen.invoker.BaseApi; +import school.hei.haapi.service.documenso.gen.model.DocumentGet200Response; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class DocumentApi extends BaseApi { + + public DocumentApi() { + super(new ApiClient()); + } + + public DocumentApi(ApiClient apiClient) { + super(apiClient); + } + + /** + * Download document Downloads the document. \"signed\" returns the completed document + * with signatures, \"original\" returns the original uploaded document. + * + *

200 - Successful response + * + * @param documentId (required) + * @param version (optional, default to signed) + * @return File + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public File documentDownload(BigDecimal documentId, String version) throws RestClientException { + return documentDownloadWithHttpInfo(documentId, version).getBody(); + } + + /** + * Download document Downloads the document. \"signed\" returns the completed document + * with signatures, \"original\" returns the original uploaded document. + * + *

200 - Successful response + * + * @param documentId (required) + * @param version (optional, default to signed) + * @return ResponseEntity<File> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity documentDownloadWithHttpInfo(BigDecimal documentId, String version) + throws RestClientException { + Object localVarPostBody = null; + + // verify the required parameter 'documentId' is set + if (documentId == null) { + throw new HttpClientErrorException( + HttpStatus.BAD_REQUEST, + "Missing the required parameter 'documentId' when calling documentDownload"); + } + + // create path and map variables + final Map uriVariables = new HashMap(); + uriVariables.put("documentId", documentId); + + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + localVarQueryParams.putAll(apiClient.parameterToMultiValueMap(null, "version", version)); + + final String[] localVarAccepts = {"application/pdf"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + ParameterizedTypeReference localReturnType = new ParameterizedTypeReference() {}; + return apiClient.invokeAPI( + "/document/{documentId}/download", + HttpMethod.GET, + uriVariables, + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localReturnType); + } + + /** + * Get document + * + *

200 - Successful response + * + * @param documentId (required) + * @return DocumentGet200Response + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public DocumentGet200Response documentGet(BigDecimal documentId) throws RestClientException { + return documentGetWithHttpInfo(documentId).getBody(); + } + + /** + * Get document + * + *

200 - Successful response + * + * @param documentId (required) + * @return ResponseEntity<DocumentGet200Response> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity documentGetWithHttpInfo(BigDecimal documentId) + throws RestClientException { + Object localVarPostBody = null; + + // verify the required parameter 'documentId' is set + if (documentId == null) { + throw new HttpClientErrorException( + HttpStatus.BAD_REQUEST, + "Missing the required parameter 'documentId' when calling documentGet"); + } + + // create path and map variables + final Map uriVariables = new HashMap(); + uriVariables.put("documentId", documentId); + + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + final String[] localVarAccepts = {"application/json"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + ParameterizedTypeReference localReturnType = + new ParameterizedTypeReference() {}; + return apiClient.invokeAPI( + "/document/{documentId}", + HttpMethod.GET, + uriVariables, + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localReturnType); + } + + @Override + public ResponseEntity invokeAPI( + String url, HttpMethod method, Object request, ParameterizedTypeReference returnType) + throws RestClientException { + String localVarPath = url.replace(apiClient.getBasePath(), ""); + Object localVarPostBody = request; + + final Map uriVariables = new HashMap(); + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + final String[] localVarAccepts = {"application/json"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + return apiClient.invokeAPI( + localVarPath, + method, + uriVariables, + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + returnType); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/api/TemplateApi.java b/src/main/java/school/hei/haapi/service/documenso/gen/api/TemplateApi.java new file mode 100644 index 000000000..79caecdc5 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/api/TemplateApi.java @@ -0,0 +1,284 @@ +package school.hei.haapi.service.documenso.gen.api; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; +import school.hei.haapi.service.documenso.gen.invoker.ApiClient; +import school.hei.haapi.service.documenso.gen.invoker.BaseApi; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplate200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequest; +import school.hei.haapi.service.documenso.gen.model.TemplateFindTemplates200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200Response; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateApi extends BaseApi { + + public TemplateApi() { + super(new ApiClient()); + } + + public TemplateApi(ApiClient apiClient) { + super(apiClient); + } + + /** + * Use template Use the template to create a document + * + *

200 - Successful response + * + * @param templateCreateDocumentFromTemplateRequest (required) + * @return TemplateCreateDocumentFromTemplate200Response + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public TemplateCreateDocumentFromTemplate200Response templateCreateDocumentFromTemplate( + TemplateCreateDocumentFromTemplateRequest templateCreateDocumentFromTemplateRequest) + throws RestClientException { + return templateCreateDocumentFromTemplateWithHttpInfo(templateCreateDocumentFromTemplateRequest) + .getBody(); + } + + /** + * Use template Use the template to create a document + * + *

200 - Successful response + * + * @param templateCreateDocumentFromTemplateRequest (required) + * @return ResponseEntity<TemplateCreateDocumentFromTemplate200Response> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity + templateCreateDocumentFromTemplateWithHttpInfo( + TemplateCreateDocumentFromTemplateRequest templateCreateDocumentFromTemplateRequest) + throws RestClientException { + Object localVarPostBody = templateCreateDocumentFromTemplateRequest; + + // verify the required parameter 'templateCreateDocumentFromTemplateRequest' is set + if (templateCreateDocumentFromTemplateRequest == null) { + throw new HttpClientErrorException( + HttpStatus.BAD_REQUEST, + "Missing the required parameter 'templateCreateDocumentFromTemplateRequest' when calling" + + " templateCreateDocumentFromTemplate"); + } + + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + final String[] localVarAccepts = {"application/json"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {"application/json"}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + ParameterizedTypeReference localReturnType = + new ParameterizedTypeReference() {}; + return apiClient.invokeAPI( + "/template/use", + HttpMethod.POST, + Collections.emptyMap(), + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localReturnType); + } + + /** + * Find templates + * + *

200 - Successful response + * + * @param query (optional) + * @param page (optional) + * @param perPage (optional) + * @return TemplateFindTemplates200Response + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public TemplateFindTemplates200Response templateFindTemplates( + String query, BigDecimal page, BigDecimal perPage) throws RestClientException { + return templateFindTemplatesWithHttpInfo(query, page, perPage).getBody(); + } + + /** + * Find templates + * + *

200 - Successful response + * + * @param query (optional) + * @param page (optional) + * @param perPage (optional) + * @return ResponseEntity<TemplateFindTemplates200Response> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity templateFindTemplatesWithHttpInfo( + String query, BigDecimal page, BigDecimal perPage) throws RestClientException { + Object localVarPostBody = null; + + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + localVarQueryParams.putAll(apiClient.parameterToMultiValueMap(null, "query", query)); + localVarQueryParams.putAll(apiClient.parameterToMultiValueMap(null, "page", page)); + localVarQueryParams.putAll(apiClient.parameterToMultiValueMap(null, "perPage", perPage)); + + final String[] localVarAccepts = {"application/json"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + ParameterizedTypeReference localReturnType = + new ParameterizedTypeReference() {}; + return apiClient.invokeAPI( + "/template", + HttpMethod.GET, + Collections.emptyMap(), + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localReturnType); + } + + /** + * Get template + * + *

200 - Successful response + * + * @param templateId (required) + * @return TemplateGetTemplateById200Response + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public TemplateGetTemplateById200Response templateGetTemplateById(BigDecimal templateId) + throws RestClientException { + return templateGetTemplateByIdWithHttpInfo(templateId).getBody(); + } + + /** + * Get template + * + *

200 - Successful response + * + * @param templateId (required) + * @return ResponseEntity<TemplateGetTemplateById200Response> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity templateGetTemplateByIdWithHttpInfo( + BigDecimal templateId) throws RestClientException { + Object localVarPostBody = null; + + // verify the required parameter 'templateId' is set + if (templateId == null) { + throw new HttpClientErrorException( + HttpStatus.BAD_REQUEST, + "Missing the required parameter 'templateId' when calling templateGetTemplateById"); + } + + // create path and map variables + final Map uriVariables = new HashMap(); + uriVariables.put("templateId", templateId); + + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + final String[] localVarAccepts = {"application/json"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + ParameterizedTypeReference localReturnType = + new ParameterizedTypeReference() {}; + return apiClient.invokeAPI( + "/template/{templateId}", + HttpMethod.GET, + uriVariables, + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + localReturnType); + } + + @Override + public ResponseEntity invokeAPI( + String url, HttpMethod method, Object request, ParameterizedTypeReference returnType) + throws RestClientException { + String localVarPath = url.replace(apiClient.getBasePath(), ""); + Object localVarPostBody = request; + + final Map uriVariables = new HashMap(); + final MultiValueMap localVarQueryParams = + new LinkedMultiValueMap(); + final HttpHeaders localVarHeaderParams = new HttpHeaders(); + final MultiValueMap localVarCookieParams = + new LinkedMultiValueMap(); + final MultiValueMap localVarFormParams = + new LinkedMultiValueMap(); + + final String[] localVarAccepts = {"application/json"}; + final List localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + final String[] localVarContentTypes = {}; + final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] {"apiKey"}; + + return apiClient.invokeAPI( + localVarPath, + method, + uriVariables, + localVarQueryParams, + localVarPostBody, + localVarHeaderParams, + localVarCookieParams, + localVarFormParams, + localVarAccept, + localVarContentType, + localVarAuthNames, + returnType); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ApiClient.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ApiClient.java new file mode 100644 index 000000000..d368fa765 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ApiClient.java @@ -0,0 +1,861 @@ +package school.hei.haapi.service.documenso.gen.invoker; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.InvalidMediaTypeException; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.RequestEntity.BodyBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.DefaultUriBuilderFactory; +import org.springframework.web.util.UriComponentsBuilder; +import school.hei.haapi.service.documenso.gen.invoker.auth.ApiKeyAuth; +import school.hei.haapi.service.documenso.gen.invoker.auth.Authentication; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class ApiClient { + public enum CollectionFormat { + CSV(","), + TSV("\t"), + SSV(" "), + PIPES("|"), + MULTI(null); + + private final String separator; + + private CollectionFormat(String separator) { + this.separator = separator; + } + + private String collectionToString(Collection collection) { + return StringUtils.collectionToDelimitedString(collection, separator); + } + } + + private boolean debugging = false; + + private HttpHeaders defaultHeaders = new HttpHeaders(); + private MultiValueMap defaultCookies = new LinkedMultiValueMap(); + + private int maxAttemptsForRetry = 1; + + private long waitTimeMillis = 10; + + private String basePath = "https://app.documenso.com/api/v2"; + + private RestTemplate restTemplate; + + private Map authentications; + + private DateFormat dateFormat; + + public ApiClient() { + this.restTemplate = buildRestTemplate(); + init(); + } + + public ApiClient(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + init(); + } + + protected void init() { + // Use RFC3339 format for date and datetime. + // See http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14 + this.dateFormat = new RFC3339DateFormat(); + + // Use UTC as the default time zone. + this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + // Set default User-Agent. + setUserAgent("Java-SDK"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap(); + authentications.put("apiKey", new ApiKeyAuth("header", "Authorization")); + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Get the current base path + * + * @return String the base path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set the base path, which should include the host + * + * @param basePath the base path + * @return ApiClient this client + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Get the max attempts for retry + * + * @return int the max attempts + */ + public int getMaxAttemptsForRetry() { + return maxAttemptsForRetry; + } + + /** + * Set the max attempts for retry + * + * @param maxAttemptsForRetry the max attempts for retry + * @return ApiClient this client + */ + public ApiClient setMaxAttemptsForRetry(int maxAttemptsForRetry) { + this.maxAttemptsForRetry = maxAttemptsForRetry; + return this; + } + + /** + * Get the wait time in milliseconds + * + * @return long wait time in milliseconds + */ + public long getWaitTimeMillis() { + return waitTimeMillis; + } + + /** + * Set the wait time in milliseconds + * + * @param waitTimeMillis the wait time in milliseconds + * @return ApiClient this client + */ + public ApiClient setWaitTimeMillis(long waitTimeMillis) { + this.waitTimeMillis = waitTimeMillis; + return this; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * + * @return Map the currently configured authentication types + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set API key value for the first API key authentication. + * + * @param apiKey the API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * + * @param apiKeyPrefix API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * + * @param userAgent the user agent string + * @return ApiClient this client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param name The header's name + * @param value The header's value + * @return ApiClient this client + */ + public ApiClient addDefaultHeader(String name, String value) { + if (defaultHeaders.containsKey(name)) { + defaultHeaders.remove(name); + } + defaultHeaders.add(name, value); + return this; + } + + /** + * Add a default cookie. + * + * @param name The cookie's name + * @param value The cookie's value + * @return ApiClient this client + */ + public ApiClient addDefaultCookie(String name, String value) { + if (defaultCookies.containsKey(name)) { + defaultCookies.remove(name); + } + defaultCookies.add(name, value); + return this; + } + + public void setDebugging(boolean debugging) { + List currentInterceptors = this.restTemplate.getInterceptors(); + if (debugging) { + if (currentInterceptors == null) { + currentInterceptors = new ArrayList(); + } + ClientHttpRequestInterceptor interceptor = new ApiClientHttpRequestInterceptor(); + currentInterceptors.add(interceptor); + this.restTemplate.setInterceptors(currentInterceptors); + } else { + if (currentInterceptors != null && !currentInterceptors.isEmpty()) { + Iterator iter = currentInterceptors.iterator(); + while (iter.hasNext()) { + ClientHttpRequestInterceptor interceptor = iter.next(); + if (interceptor instanceof ApiClientHttpRequestInterceptor) { + iter.remove(); + } + } + this.restTemplate.setInterceptors(currentInterceptors); + } + } + this.debugging = debugging; + } + + /** + * Check that whether debugging is enabled for this API client. + * + * @return boolean true if this client is enabled for debugging, false otherwise + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Get the date format used to parse/format date parameters. + * + * @return DateFormat format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * + * @param dateFormat Date format + * @return API client + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + return this; + } + + /** + * Parse the given string into Date object. + * + * @param str the string to parse + * @return the Date parsed from the string + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + * + * @param date the date to format + * @return the formatted date as string + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * + * @param param the object to convert + * @return String the parameter represented as a String + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate((Date) param); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for (Object o : (Collection) param) { + if (b.length() > 0) { + b.append(","); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Formats the specified collection path parameter to a string value. + * + * @param collectionFormat The collection format of the parameter. + * @param values The values of the parameter. + * @return String representation of the parameter + */ + public String collectionPathParameterToString( + CollectionFormat collectionFormat, Collection values) { + // create the value based on the collection format + if (CollectionFormat.MULTI.equals(collectionFormat)) { + // not valid for path params + return parameterToString(values); + } + + // collectionFormat is assumed to be "csv" by default + if (collectionFormat == null) { + collectionFormat = CollectionFormat.CSV; + } + + return collectionFormat.collectionToString(values); + } + + /** + * Converts a parameter to a {@link MultiValueMap} for use in REST requests + * + * @param collectionFormat The format to convert to + * @param name The name of the parameter + * @param value The parameter's value + * @return a Map containing the String value(s) of the input parameter + */ + public MultiValueMap parameterToMultiValueMap( + CollectionFormat collectionFormat, String name, Object value) { + final MultiValueMap params = new LinkedMultiValueMap(); + + if (name == null || name.isEmpty() || value == null) { + return params; + } + + if (collectionFormat == null) { + collectionFormat = CollectionFormat.CSV; + } + + if (value instanceof Map) { + @SuppressWarnings("unchecked") + final Map valuesMap = (Map) value; + for (final Entry entry : valuesMap.entrySet()) { + params.add(entry.getKey(), parameterToString(entry.getValue())); + } + return params; + } + + Collection valueCollection = null; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(name, parameterToString(value)); + return params; + } + + if (valueCollection.isEmpty()) { + return params; + } + + if (collectionFormat.equals(CollectionFormat.MULTI)) { + for (Object item : valueCollection) { + params.add(name, parameterToString(item)); + } + return params; + } + + List values = new ArrayList(); + for (Object o : valueCollection) { + values.add(parameterToString(o)); + } + params.add(name, collectionFormat.collectionToString(values)); + + return params; + } + + /** + * Check if the given {@code String} is a JSON MIME. + * + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(String mediaType) { + // "* / *" is default to JSON + if ("*/*".equals(mediaType)) { + return true; + } + + try { + return isJsonMime(MediaType.parseMediaType(mediaType)); + } catch (InvalidMediaTypeException e) { + } + return false; + } + + /** + * Check if the given MIME is a JSON MIME. JSON MIME examples: application/json application/json; + * charset=UTF8 APPLICATION/JSON + * + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(MediaType mediaType) { + return mediaType != null + && (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) + || mediaType.getSubtype().matches("^.*\\+json[;]?\\s*$")); + } + + /** + * Check if the given {@code String} is a Problem JSON MIME (RFC-7807). + * + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents Problem JSON, false otherwise + */ + public boolean isProblemJsonMime(String mediaType) { + return "application/problem+json".equalsIgnoreCase(mediaType); + } + + /** + * Select the Accept header's value from the given accepts array: if JSON exists in the given + * array, use it; otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return List The list of MediaTypes to use for the Accept header + */ + public List selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + MediaType mediaType = MediaType.parseMediaType(accept); + if (isJsonMime(mediaType) && !isProblemJsonMime(accept)) { + return Collections.singletonList(mediaType); + } + } + return MediaType.parseMediaTypes(StringUtils.arrayToCommaDelimitedString(accepts)); + } + + /** + * Select the Content-Type header's value from the given array: if JSON exists in the given array, + * use it; otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return MediaType The Content-Type header to use. If the given array is empty, JSON will be + * used. + */ + public MediaType selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return MediaType.APPLICATION_JSON; + } + for (String contentType : contentTypes) { + MediaType mediaType = MediaType.parseMediaType(contentType); + if (isJsonMime(mediaType)) { + return mediaType; + } + } + return MediaType.parseMediaType(contentTypes[0]); + } + + /** + * Select the body to use for the request + * + * @param obj the body object + * @param formParams the form parameters + * @param contentType the content type of the request + * @return Object the selected body + */ + protected Object selectBody( + Object obj, MultiValueMap formParams, MediaType contentType) { + boolean isForm = + MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType) + || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType); + return isForm ? formParams : obj; + } + + /** + * Expand path template with variables + * + * @param pathTemplate path template with placeholders + * @param variables variables to replace + * @return path with placeholders replaced by variables + */ + public String expandPath(String pathTemplate, Map variables) { + return restTemplate.getUriTemplateHandler().expand(pathTemplate, variables).toString(); + } + + /** + * Include queryParams in uriParams taking into account the paramName + * + * @param queryParams The query parameters + * @param uriParams The path parameters return templatized query string + */ + public String generateQueryUri( + MultiValueMap queryParams, Map uriParams) { + StringBuilder queryBuilder = new StringBuilder(); + queryParams.forEach( + (name, values) -> { + try { + final String encodedName = URLEncoder.encode(name.toString(), "UTF-8"); + if (CollectionUtils.isEmpty(values)) { + if (queryBuilder.length() != 0) { + queryBuilder.append('&'); + } + queryBuilder.append(encodedName); + } else { + int valueItemCounter = 0; + for (Object value : values) { + if (queryBuilder.length() != 0) { + queryBuilder.append('&'); + } + queryBuilder.append(encodedName); + if (value != null) { + String templatizedKey = encodedName + valueItemCounter++; + uriParams.put(templatizedKey, value.toString()); + queryBuilder.append('=').append("{").append(templatizedKey).append("}"); + } + } + } + } catch (UnsupportedEncodingException e) { + + } + }); + return queryBuilder.toString(); + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param the return type to use + * @param path The sub-path of the HTTP URL + * @param method The request method + * @param pathParams The path parameters + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param cookieParams The cookie parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType The return type into which to deserialize the response + * @return ResponseEntity<T> The response of the chosen type + */ + public ResponseEntity invokeAPI( + String path, + HttpMethod method, + Map pathParams, + MultiValueMap queryParams, + Object body, + HttpHeaders headerParams, + MultiValueMap cookieParams, + MultiValueMap formParams, + List accept, + MediaType contentType, + String[] authNames, + ParameterizedTypeReference returnType) + throws RestClientException { + updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); + + Map uriParams = new HashMap<>(); + uriParams.putAll(pathParams); + + String finalUri = path; + + if (queryParams != null && !queryParams.isEmpty()) { + // Include queryParams in uriParams taking into account the paramName + String queryUri = generateQueryUri(queryParams, uriParams); + // Append to finalUri the templatized query string like "?param1={param1Value}&....... + finalUri += "?" + queryUri; + } + String expandedPath = this.expandPath(finalUri, uriParams); + final UriComponentsBuilder builder = + UriComponentsBuilder.fromHttpUrl(basePath).path(expandedPath); + + URI uri; + try { + uri = new URI(builder.build().toUriString()); + } catch (URISyntaxException ex) { + throw new RestClientException("Could not build URL: " + builder.toUriString(), ex); + } + + final BodyBuilder requestBuilder = + RequestEntity.method( + method, UriComponentsBuilder.fromHttpUrl(basePath).toUriString() + finalUri, uriParams); + if (accept != null) { + requestBuilder.accept(accept.toArray(new MediaType[accept.size()])); + } + if (contentType != null) { + requestBuilder.contentType(contentType); + } + + addHeadersToRequest(headerParams, requestBuilder); + addHeadersToRequest(defaultHeaders, requestBuilder); + addCookiesToRequest(cookieParams, requestBuilder); + addCookiesToRequest(defaultCookies, requestBuilder); + + RequestEntity requestEntity = + requestBuilder.body(selectBody(body, formParams, contentType)); + + ResponseEntity responseEntity = null; + int attempts = 0; + while (attempts < maxAttemptsForRetry) { + try { + responseEntity = restTemplate.exchange(requestEntity, returnType); + break; + } catch (HttpServerErrorException | HttpClientErrorException ex) { + if (ex instanceof HttpServerErrorException + || ((HttpClientErrorException) ex) + .getStatusCode() + .equals(HttpStatus.TOO_MANY_REQUESTS)) { + attempts++; + if (attempts < maxAttemptsForRetry) { + try { + Thread.sleep(waitTimeMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } else { + throw ex; + } + } else { + throw ex; + } + } + } + + if (responseEntity == null) { + throw new RestClientException("ResponseEntity is null"); + } + + if (responseEntity.getStatusCode().is2xxSuccessful()) { + return responseEntity; + } else { + // The error handler built into the RestTemplate should handle 400 and 500 series errors. + throw new RestClientException( + "API returned " + + responseEntity.getStatusCode() + + " and it wasn't handled by the RestTemplate error handler"); + } + } + + /** + * Add headers to the request that is being built + * + * @param headers The headers to add + * @param requestBuilder The current request + */ + protected void addHeadersToRequest(HttpHeaders headers, BodyBuilder requestBuilder) { + for (Entry> entry : headers.entrySet()) { + List values = entry.getValue(); + for (String value : values) { + if (value != null) { + requestBuilder.header(entry.getKey(), value); + } + } + } + } + + /** + * Add cookies to the request that is being built + * + * @param cookies The cookies to add + * @param requestBuilder The current request + */ + protected void addCookiesToRequest( + MultiValueMap cookies, BodyBuilder requestBuilder) { + if (!cookies.isEmpty()) { + requestBuilder.header("Cookie", buildCookieHeader(cookies)); + } + } + + /** + * Build cookie header. Keeps a single value per cookie (as per RFC6265 section 5.3). + * + * @param cookies map all cookies + * @return header string for cookies. + */ + private String buildCookieHeader(MultiValueMap cookies) { + final StringBuilder cookieValue = new StringBuilder(); + String delimiter = ""; + for (final Map.Entry> entry : cookies.entrySet()) { + final String value = entry.getValue().get(entry.getValue().size() - 1); + cookieValue.append(String.format("%s%s=%s", delimiter, entry.getKey(), value)); + delimiter = "; "; + } + return cookieValue.toString(); + } + + /** + * Build the RestTemplate used to make HTTP requests. + * + * @return RestTemplate + */ + protected RestTemplate buildRestTemplate() { + RestTemplate restTemplate = new RestTemplate(); + // This allows us to read the response more than once - Necessary for debugging. + restTemplate.setRequestFactory( + new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory())); + + // disable default URL encoding + DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(); + uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); + restTemplate.setUriTemplateHandler(uriBuilderFactory); + return restTemplate; + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams The query parameters + * @param headerParams The header parameters + */ + protected void updateParamsForAuth( + String[] authNames, + MultiValueMap queryParams, + HttpHeaders headerParams, + MultiValueMap cookieParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) { + throw new RestClientException("Authentication undefined: " + authName); + } + auth.applyToParams(queryParams, headerParams, cookieParams); + } + } + + private class ApiClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + private final Log log = LogFactory.getLog(ApiClientHttpRequestInterceptor.class); + + @Override + public ClientHttpResponse intercept( + HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + logRequest(request, body); + ClientHttpResponse response = execution.execute(request, body); + logResponse(response); + return response; + } + + private void logRequest(HttpRequest request, byte[] body) throws UnsupportedEncodingException { + log.info("URI: " + request.getURI()); + log.info("HTTP Method: " + request.getMethod()); + log.info("HTTP Headers: " + headersToString(request.getHeaders())); + log.info("Request Body: " + new String(body, StandardCharsets.UTF_8)); + } + + private void logResponse(ClientHttpResponse response) throws IOException { + log.info("HTTP Status Code: " + response.getStatusCode().value()); + log.info("Status Text: " + response.getStatusText()); + log.info("HTTP Headers: " + headersToString(response.getHeaders())); + log.info("Response Body: " + bodyToString(response.getBody())); + } + + private String headersToString(HttpHeaders headers) { + if (headers == null || headers.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (Entry> entry : headers.entrySet()) { + builder.append(entry.getKey()).append("=["); + for (String value : entry.getValue()) { + builder.append(value).append(","); + } + builder.setLength(builder.length() - 1); // Get rid of trailing comma + builder.append("],"); + } + builder.setLength(builder.length() - 1); // Get rid of trailing comma + return builder.toString(); + } + + private String bodyToString(InputStream body) throws IOException { + StringBuilder builder = new StringBuilder(); + BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(body, StandardCharsets.UTF_8)); + String line = bufferedReader.readLine(); + while (line != null) { + builder.append(line).append(System.lineSeparator()); + line = bufferedReader.readLine(); + } + bufferedReader.close(); + return builder.toString(); + } + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/BaseApi.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/BaseApi.java new file mode 100644 index 000000000..49190d107 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/BaseApi.java @@ -0,0 +1,90 @@ +package school.hei.haapi.service.documenso.gen.invoker; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClientException; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public abstract class BaseApi { + + protected ApiClient apiClient; + + public BaseApi() { + this(new ApiClient()); + } + + public BaseApi(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public ApiClient getApiClient() { + return apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for + * subsequent requests. + * + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @return ResponseEntity<Void> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity invokeAPI(String url, HttpMethod method) throws RestClientException { + return invokeAPI(url, method, null, new ParameterizedTypeReference() {}); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for + * subsequent requests. + * + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @return ResponseEntity<Void> + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity invokeAPI(String url, HttpMethod method, Object request) + throws RestClientException { + return invokeAPI(url, method, request, new ParameterizedTypeReference() {}); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for + * subsequent requests. + * + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param returnType The return type. + * @return ResponseEntity in the specified type. + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public ResponseEntity invokeAPI( + String url, HttpMethod method, ParameterizedTypeReference returnType) + throws RestClientException { + return invokeAPI(url, method, null, returnType); + } + + /** + * Directly invoke the API for the given URL. Useful if the API returns direct links/URLs for + * subsequent requests. + * + * @param url The URL for the request, either full URL or only the path. + * @param method The HTTP method for the request. + * @param request The request object. + * @param returnType The return type. + * @return ResponseEntity in the specified type. + * @throws RestClientException if an error occurs while attempting to invoke the API + */ + public abstract ResponseEntity invokeAPI( + String url, HttpMethod method, Object request, ParameterizedTypeReference returnType) + throws RestClientException; +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/RFC3339DateFormat.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/RFC3339DateFormat.java new file mode 100644 index 000000000..c37f87f02 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/RFC3339DateFormat.java @@ -0,0 +1,59 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.invoker; + +import com.fasterxml.jackson.databind.util.StdDateFormat; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class RFC3339DateFormat extends DateFormat { + private static final long serialVersionUID = 1L; + private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC"); + + private final StdDateFormat fmt = + new StdDateFormat().withTimeZone(TIMEZONE_Z).withColonInTimeZone(true); + + public RFC3339DateFormat() { + this.calendar = new GregorianCalendar(); + this.numberFormat = new DecimalFormat(); + } + + @Override + public Date parse(String source) { + return parse(source, new ParsePosition(0)); + } + + @Override + public Date parse(String source, ParsePosition pos) { + return fmt.parse(source, pos); + } + + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { + return fmt.format(date, toAppendTo, fieldPosition); + } + + @Override + public Object clone() { + return super.clone(); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ServerConfiguration.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ServerConfiguration.java new file mode 100644 index 000000000..dbc80750f --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ServerConfiguration.java @@ -0,0 +1,63 @@ +package school.hei.haapi.service.documenso.gen.invoker; + +import java.util.Map; + +/** Representing a Server configuration. */ +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class ServerConfiguration { + public String URL; + public String description; + public Map variables; + + /** + * @param URL A URL to the target host. + * @param description A description of the host designated by the URL. + * @param variables A map between a variable name and its value. The value is used for + * substitution in the server's URL template. + */ + public ServerConfiguration( + String URL, String description, Map variables) { + this.URL = URL; + this.description = description; + this.variables = variables; + } + + /** + * Format URL template using given variables. + * + * @param variables A map between a variable name and its value. + * @return Formatted URL. + */ + public String URL(Map variables) { + String url = this.URL; + + // go through variables and replace placeholders + for (Map.Entry variable : this.variables.entrySet()) { + String name = variable.getKey(); + ServerVariable serverVariable = variable.getValue(); + String value = serverVariable.defaultValue; + + if (variables != null && variables.containsKey(name)) { + value = variables.get(name); + if (serverVariable.enumValues.size() > 0 && !serverVariable.enumValues.contains(value)) { + throw new IllegalArgumentException( + "The variable " + name + " in the server URL has invalid value " + value + "."); + } + } + url = url.replace("{" + name + "}", value); + } + return url; + } + + /** + * Format URL template using default server variables. + * + * @return Formatted URL. + */ + public String URL() { + return URL(null); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ServerVariable.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ServerVariable.java new file mode 100644 index 000000000..bc98cc6f4 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/ServerVariable.java @@ -0,0 +1,26 @@ +package school.hei.haapi.service.documenso.gen.invoker; + +import java.util.HashSet; + +/** Representing a Server Variable for server URL template substitution. */ +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class ServerVariable { + public String description; + public String defaultValue; + public HashSet enumValues = null; + + /** + * @param description A description for the server variable. + * @param defaultValue The default value to use for substitution. + * @param enumValues An enumeration of string values to be used if the substitution options are + * from a limited set. + */ + public ServerVariable(String description, String defaultValue, HashSet enumValues) { + this.description = description; + this.defaultValue = defaultValue; + this.enumValues = enumValues; + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/ApiKeyAuth.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/ApiKeyAuth.java new file mode 100644 index 000000000..c4131a9ec --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/ApiKeyAuth.java @@ -0,0 +1,68 @@ +package school.hei.haapi.service.documenso.gen.invoker.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class ApiKeyAuth implements Authentication { + private final String location; + private final String paramName; + + private String apiKey; + private String apiKeyPrefix; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKeyPrefix() { + return apiKeyPrefix; + } + + public void setApiKeyPrefix(String apiKeyPrefix) { + this.apiKeyPrefix = apiKeyPrefix; + } + + @Override + public void applyToParams( + MultiValueMap queryParams, + HttpHeaders headerParams, + MultiValueMap cookieParams) { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if (location.equals("query")) { + queryParams.add(paramName, value); + } else if (location.equals("header")) { + headerParams.add(paramName, value); + } else if (location.equals("cookie")) { + cookieParams.add(paramName, value); + } + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/Authentication.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/Authentication.java new file mode 100644 index 000000000..8e008dcbf --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/Authentication.java @@ -0,0 +1,18 @@ +package school.hei.haapi.service.documenso.gen.invoker.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +public interface Authentication { + /** + * Apply authentication settings to header and / or query parameters. + * + * @param queryParams The query parameters for the request + * @param headerParams The header parameters for the request + * @param cookieParams The cookie parameters for the request + */ + public void applyToParams( + MultiValueMap queryParams, + HttpHeaders headerParams, + MultiValueMap cookieParams); +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/HttpBasicAuth.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/HttpBasicAuth.java new file mode 100644 index 000000000..f75300eff --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/HttpBasicAuth.java @@ -0,0 +1,45 @@ +package school.hei.haapi.service.documenso.gen.invoker.auth; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class HttpBasicAuth implements Authentication { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public void applyToParams( + MultiValueMap queryParams, + HttpHeaders headerParams, + MultiValueMap cookieParams) { + if (username == null && password == null) { + return; + } + String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); + headerParams.add( + HttpHeaders.AUTHORIZATION, + "Basic " + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/HttpBearerAuth.java b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/HttpBearerAuth.java new file mode 100644 index 000000000..896afcf39 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/invoker/auth/HttpBearerAuth.java @@ -0,0 +1,67 @@ +package school.hei.haapi.service.documenso.gen.invoker.auth; + +import java.util.Optional; +import java.util.function.Supplier; +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class HttpBearerAuth implements Authentication { + private final String scheme; + private Supplier tokenSupplier; + + public HttpBearerAuth(String scheme) { + this.scheme = scheme; + } + + /** + * Gets the token, which together with the scheme, will be sent as the value of the Authorization + * header. + * + * @return The bearer token + */ + public String getBearerToken() { + return tokenSupplier.get(); + } + + /** + * Sets the token, which together with the scheme, will be sent as the value of the Authorization + * header. + * + * @param bearerToken The bearer token to send in the Authorization header + */ + public void setBearerToken(String bearerToken) { + this.tokenSupplier = () -> bearerToken; + } + + /** + * Sets the supplier of tokens, which together with the scheme, will be sent as the value of the + * Authorization header. + * + * @param tokenSupplier The supplier of bearer tokens to send in the Authorization header + */ + public void setBearerToken(Supplier tokenSupplier) { + this.tokenSupplier = tokenSupplier; + } + + @Override + public void applyToParams( + MultiValueMap queryParams, + HttpHeaders headerParams, + MultiValueMap cookieParams) { + String bearerToken = Optional.ofNullable(tokenSupplier).map(Supplier::get).orElse(null); + if (bearerToken == null) { + return; + } + headerParams.add( + HttpHeaders.AUTHORIZATION, + (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken); + } + + private static String upperCaseBearer(String scheme) { + return ("bearer".equalsIgnoreCase(scheme)) ? "Bearer" : scheme; + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/DocumentGet200Response.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/DocumentGet200Response.java new file mode 100644 index 000000000..f3b1b7cbd --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/DocumentGet200Response.java @@ -0,0 +1,258 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** DocumentGet200Response */ +@JsonPropertyOrder({ + DocumentGet200Response.JSON_PROPERTY_ID, + DocumentGet200Response.JSON_PROPERTY_STATUS, + DocumentGet200Response.JSON_PROPERTY_TITLE, + DocumentGet200Response.JSON_PROPERTY_CREATED_AT, + DocumentGet200Response.JSON_PROPERTY_COMPLETED_AT +}) +@JsonTypeName("document_get_200_response") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class DocumentGet200Response implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + /** Gets or Sets status */ + public enum StatusEnum { + DRAFT("DRAFT"), + + PENDING("PENDING"), + + COMPLETED("COMPLETED"), + + REJECTED("REJECTED"); + + private String value; + + StatusEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static StatusEnum fromValue(String value) { + for (StatusEnum b : StatusEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + } + + public static final String JSON_PROPERTY_STATUS = "status"; + private StatusEnum status; + + public static final String JSON_PROPERTY_TITLE = "title"; + private String title; + + public static final String JSON_PROPERTY_CREATED_AT = "createdAt"; + private String createdAt; + + public static final String JSON_PROPERTY_COMPLETED_AT = "completedAt"; + private String completedAt; + + public DocumentGet200Response() {} + + public DocumentGet200Response id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public DocumentGet200Response status(StatusEnum status) { + + this.status = status; + return this; + } + + /** + * Get status + * + * @return status + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public StatusEnum getStatus() { + return status; + } + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setStatus(StatusEnum status) { + this.status = status; + } + + public DocumentGet200Response title(String title) { + + this.title = title; + return this; + } + + /** + * Get title + * + * @return title + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getTitle() { + return title; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setTitle(String title) { + this.title = title; + } + + public DocumentGet200Response createdAt(String createdAt) { + + this.createdAt = createdAt; + return this; + } + + /** + * Get createdAt + * + * @return createdAt + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_CREATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getCreatedAt() { + return createdAt; + } + + @JsonProperty(JSON_PROPERTY_CREATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public DocumentGet200Response completedAt(String completedAt) { + + this.completedAt = completedAt; + return this; + } + + /** + * Get completedAt + * + * @return completedAt + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_COMPLETED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getCompletedAt() { + return completedAt; + } + + @JsonProperty(JSON_PROPERTY_COMPLETED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setCompletedAt(String completedAt) { + this.completedAt = completedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DocumentGet200Response documentGet200Response = (DocumentGet200Response) o; + return Objects.equals(this.id, documentGet200Response.id) + && Objects.equals(this.status, documentGet200Response.status) + && Objects.equals(this.title, documentGet200Response.title) + && Objects.equals(this.createdAt, documentGet200Response.createdAt) + && Objects.equals(this.completedAt, documentGet200Response.completedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, status, title, createdAt, completedAt); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class DocumentGet200Response {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" createdAt: ").append(toIndentedString(createdAt)).append("\n"); + sb.append(" completedAt: ").append(toIndentedString(completedAt)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplate200Response.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplate200Response.java new file mode 100644 index 000000000..5d65d260a --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplate200Response.java @@ -0,0 +1,274 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** TemplateCreateDocumentFromTemplate200Response */ +@JsonPropertyOrder({ + TemplateCreateDocumentFromTemplate200Response.JSON_PROPERTY_ID, + TemplateCreateDocumentFromTemplate200Response.JSON_PROPERTY_STATUS, + TemplateCreateDocumentFromTemplate200Response.JSON_PROPERTY_TITLE, + TemplateCreateDocumentFromTemplate200Response.JSON_PROPERTY_CREATED_AT, + TemplateCreateDocumentFromTemplate200Response.JSON_PROPERTY_RECIPIENTS +}) +@JsonTypeName("template_createDocumentFromTemplate_200_response") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateCreateDocumentFromTemplate200Response implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + /** Gets or Sets status */ + public enum StatusEnum { + DRAFT("DRAFT"), + + PENDING("PENDING"), + + COMPLETED("COMPLETED"), + + REJECTED("REJECTED"); + + private String value; + + StatusEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static StatusEnum fromValue(String value) { + for (StatusEnum b : StatusEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + } + + public static final String JSON_PROPERTY_STATUS = "status"; + private StatusEnum status; + + public static final String JSON_PROPERTY_TITLE = "title"; + private String title; + + public static final String JSON_PROPERTY_CREATED_AT = "createdAt"; + private String createdAt; + + public static final String JSON_PROPERTY_RECIPIENTS = "recipients"; + private List recipients = + new ArrayList<>(); + + public TemplateCreateDocumentFromTemplate200Response() {} + + public TemplateCreateDocumentFromTemplate200Response id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateCreateDocumentFromTemplate200Response status(StatusEnum status) { + + this.status = status; + return this; + } + + /** + * Get status + * + * @return status + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public StatusEnum getStatus() { + return status; + } + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setStatus(StatusEnum status) { + this.status = status; + } + + public TemplateCreateDocumentFromTemplate200Response title(String title) { + + this.title = title; + return this; + } + + /** + * Get title + * + * @return title + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getTitle() { + return title; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setTitle(String title) { + this.title = title; + } + + public TemplateCreateDocumentFromTemplate200Response createdAt(String createdAt) { + + this.createdAt = createdAt; + return this; + } + + /** + * Get createdAt + * + * @return createdAt + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_CREATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getCreatedAt() { + return createdAt; + } + + @JsonProperty(JSON_PROPERTY_CREATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public TemplateCreateDocumentFromTemplate200Response recipients( + List recipients) { + + this.recipients = recipients; + return this; + } + + public TemplateCreateDocumentFromTemplate200Response addRecipientsItem( + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner recipientsItem) { + if (this.recipients == null) { + this.recipients = new ArrayList<>(); + } + this.recipients.add(recipientsItem); + return this; + } + + /** + * Get recipients + * + * @return recipients + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_RECIPIENTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public List getRecipients() { + return recipients; + } + + @JsonProperty(JSON_PROPERTY_RECIPIENTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setRecipients( + List recipients) { + this.recipients = recipients; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateCreateDocumentFromTemplate200Response templateCreateDocumentFromTemplate200Response = + (TemplateCreateDocumentFromTemplate200Response) o; + return Objects.equals(this.id, templateCreateDocumentFromTemplate200Response.id) + && Objects.equals(this.status, templateCreateDocumentFromTemplate200Response.status) + && Objects.equals(this.title, templateCreateDocumentFromTemplate200Response.title) + && Objects.equals(this.createdAt, templateCreateDocumentFromTemplate200Response.createdAt) + && Objects.equals( + this.recipients, templateCreateDocumentFromTemplate200Response.recipients); + } + + @Override + public int hashCode() { + return Objects.hash(id, status, title, createdAt, recipients); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateCreateDocumentFromTemplate200Response {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" createdAt: ").append(toIndentedString(createdAt)).append("\n"); + sb.append(" recipients: ").append(toIndentedString(recipients)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.java new file mode 100644 index 000000000..5fe77aae6 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.java @@ -0,0 +1,266 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** TemplateCreateDocumentFromTemplate200ResponseRecipientsInner */ +@JsonPropertyOrder({ + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.JSON_PROPERTY_ID, + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.JSON_PROPERTY_EMAIL, + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.JSON_PROPERTY_NAME, + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.JSON_PROPERTY_ROLE, + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner.JSON_PROPERTY_TOKEN +}) +@JsonTypeName("template_createDocumentFromTemplate_200_response_recipients_inner") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateCreateDocumentFromTemplate200ResponseRecipientsInner implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + public static final String JSON_PROPERTY_EMAIL = "email"; + private String email; + + public static final String JSON_PROPERTY_NAME = "name"; + private String name; + + /** Gets or Sets role */ + public enum RoleEnum { + CC("CC"), + + SIGNER("SIGNER"), + + VIEWER("VIEWER"), + + APPROVER("APPROVER"), + + ASSISTANT("ASSISTANT"); + + private String value; + + RoleEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static RoleEnum fromValue(String value) { + for (RoleEnum b : RoleEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + } + + public static final String JSON_PROPERTY_ROLE = "role"; + private RoleEnum role; + + public static final String JSON_PROPERTY_TOKEN = "token"; + private String token; + + public TemplateCreateDocumentFromTemplate200ResponseRecipientsInner() {} + + public TemplateCreateDocumentFromTemplate200ResponseRecipientsInner id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateCreateDocumentFromTemplate200ResponseRecipientsInner email(String email) { + + this.email = email; + return this; + } + + /** + * Get email + * + * @return email + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_EMAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getEmail() { + return email; + } + + @JsonProperty(JSON_PROPERTY_EMAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setEmail(String email) { + this.email = email; + } + + public TemplateCreateDocumentFromTemplate200ResponseRecipientsInner name(String name) { + + this.name = name; + return this; + } + + /** + * Get name + * + * @return name + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getName() { + return name; + } + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setName(String name) { + this.name = name; + } + + public TemplateCreateDocumentFromTemplate200ResponseRecipientsInner role(RoleEnum role) { + + this.role = role; + return this; + } + + /** + * Get role + * + * @return role + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_ROLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public RoleEnum getRole() { + return role; + } + + @JsonProperty(JSON_PROPERTY_ROLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setRole(RoleEnum role) { + this.role = role; + } + + public TemplateCreateDocumentFromTemplate200ResponseRecipientsInner token(String token) { + + this.token = token; + return this; + } + + /** + * Get token + * + * @return token + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_TOKEN) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getToken() { + return token; + } + + @JsonProperty(JSON_PROPERTY_TOKEN) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setToken(String token) { + this.token = token; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateCreateDocumentFromTemplate200ResponseRecipientsInner + templateCreateDocumentFromTemplate200ResponseRecipientsInner = + (TemplateCreateDocumentFromTemplate200ResponseRecipientsInner) o; + return Objects.equals(this.id, templateCreateDocumentFromTemplate200ResponseRecipientsInner.id) + && Objects.equals( + this.email, templateCreateDocumentFromTemplate200ResponseRecipientsInner.email) + && Objects.equals( + this.name, templateCreateDocumentFromTemplate200ResponseRecipientsInner.name) + && Objects.equals( + this.role, templateCreateDocumentFromTemplate200ResponseRecipientsInner.role) + && Objects.equals( + this.token, templateCreateDocumentFromTemplate200ResponseRecipientsInner.token); + } + + @Override + public int hashCode() { + return Objects.hash(id, email, name, role, token); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateCreateDocumentFromTemplate200ResponseRecipientsInner {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" email: ").append(toIndentedString(email)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" role: ").append(toIndentedString(role)).append("\n"); + sb.append(" token: ").append(toIndentedString(token)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequest.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequest.java new file mode 100644 index 000000000..8ab872f52 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequest.java @@ -0,0 +1,187 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** TemplateCreateDocumentFromTemplateRequest */ +@JsonPropertyOrder({ + TemplateCreateDocumentFromTemplateRequest.JSON_PROPERTY_TEMPLATE_ID, + TemplateCreateDocumentFromTemplateRequest.JSON_PROPERTY_RECIPIENTS, + TemplateCreateDocumentFromTemplateRequest.JSON_PROPERTY_PREFILL_FIELDS +}) +@JsonTypeName("template_createDocumentFromTemplate_request") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateCreateDocumentFromTemplateRequest implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_TEMPLATE_ID = "templateId"; + private BigDecimal templateId; + + public static final String JSON_PROPERTY_RECIPIENTS = "recipients"; + private List recipients = + new ArrayList<>(); + + public static final String JSON_PROPERTY_PREFILL_FIELDS = "prefillFields"; + private List prefillFields = + new ArrayList<>(); + + public TemplateCreateDocumentFromTemplateRequest() {} + + public TemplateCreateDocumentFromTemplateRequest templateId(BigDecimal templateId) { + + this.templateId = templateId; + return this; + } + + /** + * Get templateId + * + * @return templateId + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_TEMPLATE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getTemplateId() { + return templateId; + } + + @JsonProperty(JSON_PROPERTY_TEMPLATE_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setTemplateId(BigDecimal templateId) { + this.templateId = templateId; + } + + public TemplateCreateDocumentFromTemplateRequest recipients( + List recipients) { + + this.recipients = recipients; + return this; + } + + public TemplateCreateDocumentFromTemplateRequest addRecipientsItem( + TemplateCreateDocumentFromTemplateRequestRecipientsInner recipientsItem) { + if (this.recipients == null) { + this.recipients = new ArrayList<>(); + } + this.recipients.add(recipientsItem); + return this; + } + + /** + * Get recipients + * + * @return recipients + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_RECIPIENTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public List getRecipients() { + return recipients; + } + + @JsonProperty(JSON_PROPERTY_RECIPIENTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setRecipients( + List recipients) { + this.recipients = recipients; + } + + public TemplateCreateDocumentFromTemplateRequest prefillFields( + List prefillFields) { + + this.prefillFields = prefillFields; + return this; + } + + public TemplateCreateDocumentFromTemplateRequest addPrefillFieldsItem( + TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner prefillFieldsItem) { + if (this.prefillFields == null) { + this.prefillFields = new ArrayList<>(); + } + this.prefillFields.add(prefillFieldsItem); + return this; + } + + /** + * Get prefillFields + * + * @return prefillFields + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PREFILL_FIELDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public List getPrefillFields() { + return prefillFields; + } + + @JsonProperty(JSON_PROPERTY_PREFILL_FIELDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setPrefillFields( + List prefillFields) { + this.prefillFields = prefillFields; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateCreateDocumentFromTemplateRequest templateCreateDocumentFromTemplateRequest = + (TemplateCreateDocumentFromTemplateRequest) o; + return Objects.equals(this.templateId, templateCreateDocumentFromTemplateRequest.templateId) + && Objects.equals(this.recipients, templateCreateDocumentFromTemplateRequest.recipients) + && Objects.equals( + this.prefillFields, templateCreateDocumentFromTemplateRequest.prefillFields); + } + + @Override + public int hashCode() { + return Objects.hash(templateId, recipients, prefillFields); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateCreateDocumentFromTemplateRequest {\n"); + sb.append(" templateId: ").append(toIndentedString(templateId)).append("\n"); + sb.append(" recipients: ").append(toIndentedString(recipients)).append("\n"); + sb.append(" prefillFields: ").append(toIndentedString(prefillFields)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.java new file mode 100644 index 000000000..30a7b10c7 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.java @@ -0,0 +1,196 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner */ +@JsonPropertyOrder({ + TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.JSON_PROPERTY_ID, + TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.JSON_PROPERTY_TYPE, + TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner.JSON_PROPERTY_VALUE +}) +@JsonTypeName("template_createDocumentFromTemplate_request_prefillFields_inner") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + /** Gets or Sets type */ + public enum TypeEnum { + TEXT("text"); + + private String value; + + TypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static TypeEnum fromValue(String value) { + for (TypeEnum b : TypeEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + private TypeEnum type; + + public static final String JSON_PROPERTY_VALUE = "value"; + private String value; + + public TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner() {} + + public TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner type(TypeEnum type) { + + this.type = type; + return this; + } + + /** + * Get type + * + * @return type + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public TypeEnum getType() { + return type; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setType(TypeEnum type) { + this.type = type; + } + + public TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner value(String value) { + + this.value = value; + return this; + } + + /** + * Get value + * + * @return value + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_VALUE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getValue() { + return value; + } + + @JsonProperty(JSON_PROPERTY_VALUE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner + templateCreateDocumentFromTemplateRequestPrefillFieldsInner = + (TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner) o; + return Objects.equals(this.id, templateCreateDocumentFromTemplateRequestPrefillFieldsInner.id) + && Objects.equals( + this.type, templateCreateDocumentFromTemplateRequestPrefillFieldsInner.type) + && Objects.equals( + this.value, templateCreateDocumentFromTemplateRequestPrefillFieldsInner.value); + } + + @Override + public int hashCode() { + return Objects.hash(id, type, value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateCreateDocumentFromTemplateRequestPrefillFieldsInner {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" value: ").append(toIndentedString(value)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequestRecipientsInner.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequestRecipientsInner.java new file mode 100644 index 000000000..faccf3391 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateCreateDocumentFromTemplateRequestRecipientsInner.java @@ -0,0 +1,162 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** TemplateCreateDocumentFromTemplateRequestRecipientsInner */ +@JsonPropertyOrder({ + TemplateCreateDocumentFromTemplateRequestRecipientsInner.JSON_PROPERTY_ID, + TemplateCreateDocumentFromTemplateRequestRecipientsInner.JSON_PROPERTY_EMAIL, + TemplateCreateDocumentFromTemplateRequestRecipientsInner.JSON_PROPERTY_NAME +}) +@JsonTypeName("template_createDocumentFromTemplate_request_recipients_inner") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateCreateDocumentFromTemplateRequestRecipientsInner implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + public static final String JSON_PROPERTY_EMAIL = "email"; + private String email; + + public static final String JSON_PROPERTY_NAME = "name"; + private String name; + + public TemplateCreateDocumentFromTemplateRequestRecipientsInner() {} + + public TemplateCreateDocumentFromTemplateRequestRecipientsInner id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateCreateDocumentFromTemplateRequestRecipientsInner email(String email) { + + this.email = email; + return this; + } + + /** + * Get email + * + * @return email + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_EMAIL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getEmail() { + return email; + } + + @JsonProperty(JSON_PROPERTY_EMAIL) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setEmail(String email) { + this.email = email; + } + + public TemplateCreateDocumentFromTemplateRequestRecipientsInner name(String name) { + + this.name = name; + return this; + } + + /** + * Get name + * + * @return name + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getName() { + return name; + } + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateCreateDocumentFromTemplateRequestRecipientsInner + templateCreateDocumentFromTemplateRequestRecipientsInner = + (TemplateCreateDocumentFromTemplateRequestRecipientsInner) o; + return Objects.equals(this.id, templateCreateDocumentFromTemplateRequestRecipientsInner.id) + && Objects.equals( + this.email, templateCreateDocumentFromTemplateRequestRecipientsInner.email) + && Objects.equals(this.name, templateCreateDocumentFromTemplateRequestRecipientsInner.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, email, name); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateCreateDocumentFromTemplateRequestRecipientsInner {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" email: ").append(toIndentedString(email)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateFindTemplates200Response.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateFindTemplates200Response.java new file mode 100644 index 000000000..7e883563d --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateFindTemplates200Response.java @@ -0,0 +1,109 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** TemplateFindTemplates200Response */ +@JsonPropertyOrder({TemplateFindTemplates200Response.JSON_PROPERTY_DATA}) +@JsonTypeName("template_findTemplates_200_response") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateFindTemplates200Response implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_DATA = "data"; + private List data = new ArrayList<>(); + + public TemplateFindTemplates200Response() {} + + public TemplateFindTemplates200Response data( + List data) { + + this.data = data; + return this; + } + + public TemplateFindTemplates200Response addDataItem( + TemplateFindTemplates200ResponseDataInner dataItem) { + if (this.data == null) { + this.data = new ArrayList<>(); + } + this.data.add(dataItem); + return this; + } + + /** + * Get data + * + * @return data + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_DATA) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public List getData() { + return data; + } + + @JsonProperty(JSON_PROPERTY_DATA) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setData(List data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateFindTemplates200Response templateFindTemplates200Response = + (TemplateFindTemplates200Response) o; + return Objects.equals(this.data, templateFindTemplates200Response.data); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateFindTemplates200Response {\n"); + sb.append(" data: ").append(toIndentedString(data)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateFindTemplates200ResponseDataInner.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateFindTemplates200ResponseDataInner.java new file mode 100644 index 000000000..b30599505 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateFindTemplates200ResponseDataInner.java @@ -0,0 +1,287 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** TemplateFindTemplates200ResponseDataInner */ +@JsonPropertyOrder({ + TemplateFindTemplates200ResponseDataInner.JSON_PROPERTY_ID, + TemplateFindTemplates200ResponseDataInner.JSON_PROPERTY_TITLE, + TemplateFindTemplates200ResponseDataInner.JSON_PROPERTY_TYPE, + TemplateFindTemplates200ResponseDataInner.JSON_PROPERTY_USER_ID, + TemplateFindTemplates200ResponseDataInner.JSON_PROPERTY_CREATED_AT, + TemplateFindTemplates200ResponseDataInner.JSON_PROPERTY_UPDATED_AT +}) +@JsonTypeName("template_findTemplates_200_response_data_inner") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateFindTemplates200ResponseDataInner implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + public static final String JSON_PROPERTY_TITLE = "title"; + private String title; + + /** Gets or Sets type */ + public enum TypeEnum { + PUBLIC("PUBLIC"), + + PRIVATE("PRIVATE"), + + ORGANISATION("ORGANISATION"); + + private String value; + + TypeEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static TypeEnum fromValue(String value) { + for (TypeEnum b : TypeEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + } + + public static final String JSON_PROPERTY_TYPE = "type"; + private TypeEnum type; + + public static final String JSON_PROPERTY_USER_ID = "userId"; + private BigDecimal userId; + + public static final String JSON_PROPERTY_CREATED_AT = "createdAt"; + private String createdAt; + + public static final String JSON_PROPERTY_UPDATED_AT = "updatedAt"; + private String updatedAt; + + public TemplateFindTemplates200ResponseDataInner() {} + + public TemplateFindTemplates200ResponseDataInner id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateFindTemplates200ResponseDataInner title(String title) { + + this.title = title; + return this; + } + + /** + * Get title + * + * @return title + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getTitle() { + return title; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setTitle(String title) { + this.title = title; + } + + public TemplateFindTemplates200ResponseDataInner type(TypeEnum type) { + + this.type = type; + return this; + } + + /** + * Get type + * + * @return type + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public TypeEnum getType() { + return type; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setType(TypeEnum type) { + this.type = type; + } + + public TemplateFindTemplates200ResponseDataInner userId(BigDecimal userId) { + + this.userId = userId; + return this; + } + + /** + * Get userId + * + * @return userId + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_USER_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getUserId() { + return userId; + } + + @JsonProperty(JSON_PROPERTY_USER_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setUserId(BigDecimal userId) { + this.userId = userId; + } + + public TemplateFindTemplates200ResponseDataInner createdAt(String createdAt) { + + this.createdAt = createdAt; + return this; + } + + /** + * Get createdAt + * + * @return createdAt + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_CREATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getCreatedAt() { + return createdAt; + } + + @JsonProperty(JSON_PROPERTY_CREATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public TemplateFindTemplates200ResponseDataInner updatedAt(String updatedAt) { + + this.updatedAt = updatedAt; + return this; + } + + /** + * Get updatedAt + * + * @return updatedAt + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_UPDATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getUpdatedAt() { + return updatedAt; + } + + @JsonProperty(JSON_PROPERTY_UPDATED_AT) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateFindTemplates200ResponseDataInner templateFindTemplates200ResponseDataInner = + (TemplateFindTemplates200ResponseDataInner) o; + return Objects.equals(this.id, templateFindTemplates200ResponseDataInner.id) + && Objects.equals(this.title, templateFindTemplates200ResponseDataInner.title) + && Objects.equals(this.type, templateFindTemplates200ResponseDataInner.type) + && Objects.equals(this.userId, templateFindTemplates200ResponseDataInner.userId) + && Objects.equals(this.createdAt, templateFindTemplates200ResponseDataInner.createdAt) + && Objects.equals(this.updatedAt, templateFindTemplates200ResponseDataInner.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, title, type, userId, createdAt, updatedAt); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateFindTemplates200ResponseDataInner {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" userId: ").append(toIndentedString(userId)).append("\n"); + sb.append(" createdAt: ").append(toIndentedString(createdAt)).append("\n"); + sb.append(" updatedAt: ").append(toIndentedString(updatedAt)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200Response.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200Response.java new file mode 100644 index 000000000..0a458284f --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200Response.java @@ -0,0 +1,242 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** TemplateGetTemplateById200Response */ +@JsonPropertyOrder({ + TemplateGetTemplateById200Response.JSON_PROPERTY_ID, + TemplateGetTemplateById200Response.JSON_PROPERTY_TITLE, + TemplateGetTemplateById200Response.JSON_PROPERTY_USER_ID, + TemplateGetTemplateById200Response.JSON_PROPERTY_RECIPIENTS, + TemplateGetTemplateById200Response.JSON_PROPERTY_FIELDS +}) +@JsonTypeName("template_getTemplateById_200_response") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateGetTemplateById200Response implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + public static final String JSON_PROPERTY_TITLE = "title"; + private String title; + + public static final String JSON_PROPERTY_USER_ID = "userId"; + private BigDecimal userId; + + public static final String JSON_PROPERTY_RECIPIENTS = "recipients"; + private List recipients = new ArrayList<>(); + + public static final String JSON_PROPERTY_FIELDS = "fields"; + private List fields = new ArrayList<>(); + + public TemplateGetTemplateById200Response() {} + + public TemplateGetTemplateById200Response id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateGetTemplateById200Response title(String title) { + + this.title = title; + return this; + } + + /** + * Get title + * + * @return title + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getTitle() { + return title; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setTitle(String title) { + this.title = title; + } + + public TemplateGetTemplateById200Response userId(BigDecimal userId) { + + this.userId = userId; + return this; + } + + /** + * Get userId + * + * @return userId + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_USER_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public BigDecimal getUserId() { + return userId; + } + + @JsonProperty(JSON_PROPERTY_USER_ID) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setUserId(BigDecimal userId) { + this.userId = userId; + } + + public TemplateGetTemplateById200Response recipients( + List recipients) { + + this.recipients = recipients; + return this; + } + + public TemplateGetTemplateById200Response addRecipientsItem( + TemplateGetTemplateById200ResponseRecipientsInner recipientsItem) { + if (this.recipients == null) { + this.recipients = new ArrayList<>(); + } + this.recipients.add(recipientsItem); + return this; + } + + /** + * Get recipients + * + * @return recipients + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_RECIPIENTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public List getRecipients() { + return recipients; + } + + @JsonProperty(JSON_PROPERTY_RECIPIENTS) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setRecipients(List recipients) { + this.recipients = recipients; + } + + public TemplateGetTemplateById200Response fields( + List fields) { + + this.fields = fields; + return this; + } + + public TemplateGetTemplateById200Response addFieldsItem( + TemplateGetTemplateById200ResponseFieldsInner fieldsItem) { + if (this.fields == null) { + this.fields = new ArrayList<>(); + } + this.fields.add(fieldsItem); + return this; + } + + /** + * Get fields + * + * @return fields + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_FIELDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public List getFields() { + return fields; + } + + @JsonProperty(JSON_PROPERTY_FIELDS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setFields(List fields) { + this.fields = fields; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateGetTemplateById200Response templateGetTemplateById200Response = + (TemplateGetTemplateById200Response) o; + return Objects.equals(this.id, templateGetTemplateById200Response.id) + && Objects.equals(this.title, templateGetTemplateById200Response.title) + && Objects.equals(this.userId, templateGetTemplateById200Response.userId) + && Objects.equals(this.recipients, templateGetTemplateById200Response.recipients) + && Objects.equals(this.fields, templateGetTemplateById200Response.fields); + } + + @Override + public int hashCode() { + return Objects.hash(id, title, userId, recipients, fields); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateGetTemplateById200Response {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" userId: ").append(toIndentedString(userId)).append("\n"); + sb.append(" recipients: ").append(toIndentedString(recipients)).append("\n"); + sb.append(" fields: ").append(toIndentedString(fields)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200ResponseFieldsInner.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200ResponseFieldsInner.java new file mode 100644 index 000000000..d8d4c65ac --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200ResponseFieldsInner.java @@ -0,0 +1,251 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** TemplateGetTemplateById200ResponseFieldsInner */ +@JsonPropertyOrder({ + TemplateGetTemplateById200ResponseFieldsInner.JSON_PROPERTY_ID, + TemplateGetTemplateById200ResponseFieldsInner.JSON_PROPERTY_TYPE, + TemplateGetTemplateById200ResponseFieldsInner.JSON_PROPERTY_LABEL, + TemplateGetTemplateById200ResponseFieldsInner.JSON_PROPERTY_PLACEHOLDER, + TemplateGetTemplateById200ResponseFieldsInner.JSON_PROPERTY_PAGE, + TemplateGetTemplateById200ResponseFieldsInner.JSON_PROPERTY_POSITION_Y +}) +@JsonTypeName("template_getTemplateById_200_response_fields_inner") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateGetTemplateById200ResponseFieldsInner implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + public static final String JSON_PROPERTY_TYPE = "type"; + private String type; + + public static final String JSON_PROPERTY_LABEL = "label"; + private String label; + + public static final String JSON_PROPERTY_PLACEHOLDER = "placeholder"; + private String placeholder; + + public static final String JSON_PROPERTY_PAGE = "page"; + private BigDecimal page; + + public static final String JSON_PROPERTY_POSITION_Y = "positionY"; + private BigDecimal positionY; + + public TemplateGetTemplateById200ResponseFieldsInner() {} + + public TemplateGetTemplateById200ResponseFieldsInner id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateGetTemplateById200ResponseFieldsInner type(String type) { + + this.type = type; + return this; + } + + /** + * Get type + * + * @return type + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getType() { + return type; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setType(String type) { + this.type = type; + } + + public TemplateGetTemplateById200ResponseFieldsInner label(String label) { + + this.label = label; + return this; + } + + /** + * Get label + * + * @return label + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_LABEL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getLabel() { + return label; + } + + @JsonProperty(JSON_PROPERTY_LABEL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setLabel(String label) { + this.label = label; + } + + public TemplateGetTemplateById200ResponseFieldsInner placeholder(String placeholder) { + + this.placeholder = placeholder; + return this; + } + + /** + * Get placeholder + * + * @return placeholder + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PLACEHOLDER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getPlaceholder() { + return placeholder; + } + + @JsonProperty(JSON_PROPERTY_PLACEHOLDER) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setPlaceholder(String placeholder) { + this.placeholder = placeholder; + } + + public TemplateGetTemplateById200ResponseFieldsInner page(BigDecimal page) { + + this.page = page; + return this; + } + + /** + * Get page + * + * @return page + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_PAGE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public BigDecimal getPage() { + return page; + } + + @JsonProperty(JSON_PROPERTY_PAGE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setPage(BigDecimal page) { + this.page = page; + } + + public TemplateGetTemplateById200ResponseFieldsInner positionY(BigDecimal positionY) { + + this.positionY = positionY; + return this; + } + + /** + * Get positionY + * + * @return positionY + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_POSITION_Y) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public BigDecimal getPositionY() { + return positionY; + } + + @JsonProperty(JSON_PROPERTY_POSITION_Y) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setPositionY(BigDecimal positionY) { + this.positionY = positionY; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateGetTemplateById200ResponseFieldsInner templateGetTemplateById200ResponseFieldsInner = + (TemplateGetTemplateById200ResponseFieldsInner) o; + return Objects.equals(this.id, templateGetTemplateById200ResponseFieldsInner.id) + && Objects.equals(this.type, templateGetTemplateById200ResponseFieldsInner.type) + && Objects.equals(this.label, templateGetTemplateById200ResponseFieldsInner.label) + && Objects.equals( + this.placeholder, templateGetTemplateById200ResponseFieldsInner.placeholder) + && Objects.equals(this.page, templateGetTemplateById200ResponseFieldsInner.page) + && Objects.equals(this.positionY, templateGetTemplateById200ResponseFieldsInner.positionY); + } + + @Override + public int hashCode() { + return Objects.hash(id, type, label, placeholder, page, positionY); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateGetTemplateById200ResponseFieldsInner {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" label: ").append(toIndentedString(label)).append("\n"); + sb.append(" placeholder: ").append(toIndentedString(placeholder)).append("\n"); + sb.append(" page: ").append(toIndentedString(page)).append("\n"); + sb.append(" positionY: ").append(toIndentedString(positionY)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200ResponseRecipientsInner.java b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200ResponseRecipientsInner.java new file mode 100644 index 000000000..c1ee13344 --- /dev/null +++ b/src/main/java/school/hei/haapi/service/documenso/gen/model/TemplateGetTemplateById200ResponseRecipientsInner.java @@ -0,0 +1,232 @@ +/* + * Documenso v2 API (client subset) + * Minimal subset of the Documenso v2 API (see \"Documenso v2 API.yaml\" for the full upstream spec) covering only the operations used by hei-admin-api's generated Documenso client: browsing templates, creating a document from a template, and downloading the signed result. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package school.hei.haapi.service.documenso.gen.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.annotation.JsonValue; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +/** TemplateGetTemplateById200ResponseRecipientsInner */ +@JsonPropertyOrder({ + TemplateGetTemplateById200ResponseRecipientsInner.JSON_PROPERTY_ID, + TemplateGetTemplateById200ResponseRecipientsInner.JSON_PROPERTY_ROLE, + TemplateGetTemplateById200ResponseRecipientsInner.JSON_PROPERTY_EMAIL, + TemplateGetTemplateById200ResponseRecipientsInner.JSON_PROPERTY_NAME +}) +@JsonTypeName("template_getTemplateById_200_response_recipients_inner") +@jakarta.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2026-08-10T16:46:27.452926300+03:00[Indian/Antananarivo]", + comments = "Generator version: 7.7.0") +public class TemplateGetTemplateById200ResponseRecipientsInner implements Serializable { + private static final long serialVersionUID = 1L; + + public static final String JSON_PROPERTY_ID = "id"; + private BigDecimal id; + + /** Gets or Sets role */ + public enum RoleEnum { + CC("CC"), + + SIGNER("SIGNER"), + + VIEWER("VIEWER"), + + APPROVER("APPROVER"), + + ASSISTANT("ASSISTANT"); + + private String value; + + RoleEnum(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + @JsonCreator + public static RoleEnum fromValue(String value) { + for (RoleEnum b : RoleEnum.values()) { + if (b.value.equals(value)) { + return b; + } + } + throw new IllegalArgumentException("Unexpected value '" + value + "'"); + } + } + + public static final String JSON_PROPERTY_ROLE = "role"; + private RoleEnum role; + + public static final String JSON_PROPERTY_EMAIL = "email"; + private String email; + + public static final String JSON_PROPERTY_NAME = "name"; + private String name; + + public TemplateGetTemplateById200ResponseRecipientsInner() {} + + public TemplateGetTemplateById200ResponseRecipientsInner id(BigDecimal id) { + + this.id = id; + return this; + } + + /** + * Get id + * + * @return id + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public BigDecimal getId() { + return id; + } + + @JsonProperty(JSON_PROPERTY_ID) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setId(BigDecimal id) { + this.id = id; + } + + public TemplateGetTemplateById200ResponseRecipientsInner role(RoleEnum role) { + + this.role = role; + return this; + } + + /** + * Get role + * + * @return role + */ + @jakarta.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ROLE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public RoleEnum getRole() { + return role; + } + + @JsonProperty(JSON_PROPERTY_ROLE) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setRole(RoleEnum role) { + this.role = role; + } + + public TemplateGetTemplateById200ResponseRecipientsInner email(String email) { + + this.email = email; + return this; + } + + /** + * Get email + * + * @return email + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_EMAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getEmail() { + return email; + } + + @JsonProperty(JSON_PROPERTY_EMAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setEmail(String email) { + this.email = email; + } + + public TemplateGetTemplateById200ResponseRecipientsInner name(String name) { + + this.name = name; + return this; + } + + /** + * Get name + * + * @return name + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getName() { + return name; + } + + @JsonProperty(JSON_PROPERTY_NAME) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TemplateGetTemplateById200ResponseRecipientsInner + templateGetTemplateById200ResponseRecipientsInner = + (TemplateGetTemplateById200ResponseRecipientsInner) o; + return Objects.equals(this.id, templateGetTemplateById200ResponseRecipientsInner.id) + && Objects.equals(this.role, templateGetTemplateById200ResponseRecipientsInner.role) + && Objects.equals(this.email, templateGetTemplateById200ResponseRecipientsInner.email) + && Objects.equals(this.name, templateGetTemplateById200ResponseRecipientsInner.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, role, email, name); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TemplateGetTemplateById200ResponseRecipientsInner {\n"); + sb.append(" id: ").append(toIndentedString(id)).append("\n"); + sb.append(" role: ").append(toIndentedString(role)).append("\n"); + sb.append(" email: ").append(toIndentedString(email)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index aa0b06a67..f1c2d8ed5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -39,4 +39,7 @@ casdoor.redirect-url: ${CASDOOR_REDIRECT_URL} vola.api.url: ${VOLA_API_URL:http://localhost:dummy} vola.api.key: ${VOLA_API_KEY:dummy-key} -aws.sqs.maxReceiveCount=6 \ No newline at end of file +aws.sqs.maxReceiveCount=6 +documenso.api.url: ${DOCUMENSO_API_URL:http://localhost:dummy} +documenso.api.key: ${DOCUMENSO_API_KEY:dummy-key} +documenso.webhook.secret: ${DOCUMENSO_WEBHOOK_SECRET:dummy-secret} \ No newline at end of file diff --git a/src/main/resources/db/migration/V45_130__Add_documenso_user_id_to_user_table.sql b/src/main/resources/db/migration/V45_130__Add_documenso_user_id_to_user_table.sql new file mode 100644 index 000000000..ca9cf6e00 --- /dev/null +++ b/src/main/resources/db/migration/V45_130__Add_documenso_user_id_to_user_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE "user" + ADD COLUMN documenso_user_id BIGINT; diff --git a/src/main/resources/db/migration/V45_131__Create_documenso_template_table.sql b/src/main/resources/db/migration/V45_131__Create_documenso_template_table.sql new file mode 100644 index 000000000..a7809f3ea --- /dev/null +++ b/src/main/resources/db/migration/V45_131__Create_documenso_template_table.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS documenso_template +( + id VARCHAR + CONSTRAINT pk_documenso_template + PRIMARY KEY DEFAULT uuid_generate_v4(), + + documenso_template_id BIGINT NOT NULL UNIQUE, + + title VARCHAR NOT NULL, + + type VARCHAR, + + admin_id VARCHAR + REFERENCES "user" (id), + + creation_datetime TIMESTAMP WITH TIME ZONE DEFAULT now() + ); \ No newline at end of file diff --git a/src/main/resources/db/migration/V45_132__Create_documenso_document_table.sql b/src/main/resources/db/migration/V45_132__Create_documenso_document_table.sql new file mode 100644 index 000000000..436165b6d --- /dev/null +++ b/src/main/resources/db/migration/V45_132__Create_documenso_document_table.sql @@ -0,0 +1,26 @@ +DO +$$ +BEGIN + IF NOT EXISTS ( + SELECT FROM pg_type + WHERE typname = 'documenso_document_status' + ) THEN +CREATE TYPE "documenso_document_status" + AS ENUM ('PENDING', 'COMPLETED', 'REJECTED'); +END IF; +END +$$; + +CREATE TABLE IF NOT EXISTS documenso_document +( + id VARCHAR CONSTRAINT pk_documenso_document + PRIMARY KEY DEFAULT uuid_generate_v4(), + documenso_document_id BIGINT NOT NULL UNIQUE, + documenso_template_id VARCHAR REFERENCES documenso_template (id) NOT NULL, + student_id VARCHAR REFERENCES "user" (id) NOT NULL, + level VARCHAR, + status documenso_document_status NOT NULL DEFAULT 'PENDING', + file_info_id VARCHAR REFERENCES "file_info" (id), + creation_datetime TIMESTAMP WITH TIME ZONE DEFAULT now(), + completed_datetime TIMESTAMP WITH TIME ZONE +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V45_133__Create_documenso_document_recipient_table.sql b/src/main/resources/db/migration/V45_133__Create_documenso_document_recipient_table.sql new file mode 100644 index 000000000..1f5596586 --- /dev/null +++ b/src/main/resources/db/migration/V45_133__Create_documenso_document_recipient_table.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS documenso_document_recipient +( + id VARCHAR + CONSTRAINT pk_documenso_document_recipient + PRIMARY KEY DEFAULT uuid_generate_v4(), + + documenso_document_id VARCHAR REFERENCES documenso_document (id) NOT NULL, + user_id VARCHAR REFERENCES "user" (id) NOT NULL, + documenso_recipient_id BIGINT NOT NULL, + signing_token VARCHAR NOT NULL, + signed_datetime TIMESTAMP WITH TIME ZONE, + + CONSTRAINT uq_documenso_document_recipient_document_user + UNIQUE (documenso_document_id, user_id) +); \ No newline at end of file diff --git a/src/test/java/school/hei/haapi/integration/DocumensoIT.java b/src/test/java/school/hei/haapi/integration/DocumensoIT.java new file mode 100644 index 000000000..507519baf --- /dev/null +++ b/src/test/java/school/hei/haapi/integration/DocumensoIT.java @@ -0,0 +1,346 @@ +package school.hei.haapi.integration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static school.hei.haapi.integration.conf.FakeDataProvider.someUser; +import static school.hei.haapi.integration.conf.TestUtils.ADMIN1_TOKEN; +import static school.hei.haapi.integration.conf.TestUtils.MONITOR1_TOKEN; +import static school.hei.haapi.integration.conf.TestUtils.STUDENT1_TOKEN; +import static school.hei.haapi.integration.conf.TestUtils.assertThrowsForbiddenException; +import static school.hei.haapi.integration.conf.TestUtils.setUpCasdoor; +import static school.hei.haapi.integration.conf.TestUtils.setUpCognito; +import static school.hei.haapi.model.User.Role.STUDENT; + +import java.io.File; +import java.math.BigDecimal; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import school.hei.haapi.endpoint.rest.api.DocumensoApi; +import school.hei.haapi.endpoint.rest.client.ApiClient; +import school.hei.haapi.endpoint.rest.model.CrupdateDocumensoDocument; +import school.hei.haapi.endpoint.rest.model.DocumensoDocumentStatus; +import school.hei.haapi.endpoint.rest.model.StudentLevel; +import school.hei.haapi.file.bucket.BucketComponent; +import school.hei.haapi.file.hash.FileHash; +import school.hei.haapi.file.hash.FileHashAlgorithm; +import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; +import school.hei.haapi.integration.conf.TestUtils; +import school.hei.haapi.model.DocumensoDocument; +import school.hei.haapi.model.User; +import school.hei.haapi.repository.DocumensoDocumentRecipientRepository; +import school.hei.haapi.repository.DocumensoDocumentRepository; +import school.hei.haapi.repository.MonitoringStudentRepository; +import school.hei.haapi.repository.TemplateDocumensoRepository; +import school.hei.haapi.repository.UserRepository; +import school.hei.haapi.service.documenso.DocumensoClient; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplate200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplate200ResponseRecipientsInner; +import school.hei.haapi.service.documenso.gen.model.TemplateFindTemplates200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateFindTemplates200ResponseDataInner; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200Response; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200ResponseFieldsInner; +import school.hei.haapi.service.documenso.gen.model.TemplateGetTemplateById200ResponseRecipientsInner; + +class DocumensoIT extends FacadeITMockedThirdParties { + @Autowired private UserRepository userRepository; + @Autowired private TemplateDocumensoRepository templateDocumensoRepository; + @Autowired private DocumensoDocumentRepository documensoDocumentRepository; + @Autowired private DocumensoDocumentRecipientRepository documensoDocumentRecipientRepository; + @Autowired private MonitoringStudentRepository monitoringStudentRepository; + @MockBean private DocumensoClient documensoClientMock; + @MockBean private BucketComponent bucketComponentMock; + + private User admin; + private User monitor; + private User student; + private school.hei.haapi.model.TemplateDocumenso template; + private long templateExternalId; + private String templateTitle; + + @BeforeEach + void setUp() { + setUpCasdoor(casdoorAuthServiceMock, certificateLoaderMock); + setUpCognito(cognitoComponentMock); + admin = userRepository.findById("admin1_id").orElseThrow(); + admin.setDocumensoUserId(111L); + admin = userRepository.save(admin); + monitor = userRepository.findById("monitor1_id").orElseThrow(); + student = + userRepository.save( + someUser("Fanja", STUDENT).toBuilder() + .nic("101234567890") + .address("Lot II A 12 Antananarivo") + .phone("0341234567") + .build()); + monitoringStudentRepository.saveMonitorFollowingStudents( + monitor.getId(), List.of(student.getId()), "LINKED"); + + templateExternalId = ThreadLocalRandom.current().nextLong(1_000, 1_000_000_000); + templateTitle = "Fiche d'engagement L1 " + UUID.randomUUID(); + template = + templateDocumensoRepository.save( + school.hei.haapi.model.TemplateDocumenso.builder() + .documensoTemplateId(templateExternalId) + .title(templateTitle) + .type("PRIVATE") + .build()); + } + + private DocumensoApi anApi(String token) { + return new DocumensoApi(anApiClient(token)); + } + + private ApiClient anApiClient(String token) { + return TestUtils.anApiClient(token, localPort); + } + + @Test + void admin_sync_templates_resolves_admin_by_documenso_user_id() throws Exception { + when(documensoClientMock.findTemplates(isNull(), eq(1), eq(100))) + .thenReturn( + new TemplateFindTemplates200Response() + .addDataItem( + new TemplateFindTemplates200ResponseDataInner() + .id(BigDecimal.valueOf(777)) + .title("Certificat") + .type(TemplateFindTemplates200ResponseDataInner.TypeEnum.PRIVATE) + .userId(BigDecimal.valueOf(111)))); + + var result = anApi(ADMIN1_TOKEN).syncDocumensoTemplates(); + + assertEquals(1, result.size()); + var synced = result.get(0); + assertEquals(777L, synced.getDocumensoTemplateId()); + assertEquals("Certificat", synced.getTitle()); + assertEquals(admin.getId(), synced.getAdminId()); + + var saved = templateDocumensoRepository.findByDocumensoTemplateId(777L); + assertTrue(saved.isPresent()); + assertEquals(admin.getId(), saved.get().getAdmin().getId()); + } + + @Test + void student_sync_templates_ko() { + assertThrowsForbiddenException(() -> anApi(STUDENT1_TOKEN).syncDocumensoTemplates()); + } + + @Test + void admin_generate_document_prefills_student_data_and_sends_to_monitor() throws Exception { + when(documensoClientMock.getTemplate(templateExternalId)) + .thenReturn( + new TemplateGetTemplateById200Response() + .id(BigDecimal.valueOf(templateExternalId)) + .title(templateTitle) + .addRecipientsItem( + new TemplateGetTemplateById200ResponseRecipientsInner() + .id(BigDecimal.valueOf(1)) + .role(TemplateGetTemplateById200ResponseRecipientsInner.RoleEnum.SIGNER)) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(10)) + .type("TEXT") + .label("Nom et prénoms")) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(11)) + .type("TEXT") + .label("Adresse personnelle"))); + when(documensoClientMock.useTemplate(any())) + .thenReturn( + new TemplateCreateDocumentFromTemplate200Response() + .id(BigDecimal.valueOf(999)) + .status(TemplateCreateDocumentFromTemplate200Response.StatusEnum.PENDING) + .title(templateTitle) + .addRecipientsItem( + new TemplateCreateDocumentFromTemplate200ResponseRecipientsInner() + .id(BigDecimal.valueOf(1)) + .email(monitor.getEmail()) + .name("Monitor") + .token("monitor-token"))); + + var toCreate = + new CrupdateDocumensoDocument().studentId(student.getId()).templateName(templateTitle); + + var created = anApi(ADMIN1_TOKEN).generateDocumensoDocument(toCreate); + + assertEquals(DocumensoDocumentStatus.PENDING, created.getStatus()); + assertEquals(999L, created.getDocumensoDocumentId()); + assertEquals(student.getId(), created.getStudentId()); + + var useTemplateCaptor = + org.mockito.ArgumentCaptor.forClass( + school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequest + .class); + verify(documensoClientMock).useTemplate(useTemplateCaptor.capture()); + var sentRequest = useTemplateCaptor.getValue(); + assertEquals(1, sentRequest.getRecipients().size()); + assertEquals(monitor.getEmail(), sentRequest.getRecipients().get(0).getEmail()); + var prefillByFieldId = + sentRequest.getPrefillFields().stream() + .collect( + java.util.stream.Collectors.toMap(f -> f.getId().longValue(), f -> f.getValue())); + assertEquals(student.getFirstName() + " " + student.getLastName(), prefillByFieldId.get(10L)); + assertEquals(student.getAddress(), prefillByFieldId.get(11L)); + + var monitorToken = anApi(MONITOR1_TOKEN).getDocumensoDocumentSigningToken(created.getId()); + assertEquals("monitor-token", monitorToken.getToken()); + } + + @Test + void admin_generate_document_fills_topmost_guardian_block_with_monitor_data() throws Exception { + when(documensoClientMock.getTemplate(templateExternalId)) + .thenReturn( + new TemplateGetTemplateById200Response() + .id(BigDecimal.valueOf(templateExternalId)) + .title(templateTitle) + .addRecipientsItem( + new TemplateGetTemplateById200ResponseRecipientsInner() + .id(BigDecimal.valueOf(1)) + .role(TemplateGetTemplateById200ResponseRecipientsInner.RoleEnum.SIGNER)) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(20)) + .type("TEXT") + .label("PERE/ MERE/ TUTEUR") + .page(BigDecimal.ONE) + .positionY(BigDecimal.valueOf(100))) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(21)) + .type("TEXT") + .label("Adresse personnelle") + .page(BigDecimal.ONE) + .positionY(BigDecimal.valueOf(110))) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(22)) + .type("TEXT") + .label("Téléphones") + .page(BigDecimal.ONE) + .positionY(BigDecimal.valueOf(120))) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(30)) + .type("TEXT") + .label("Nom et prénoms") + .page(BigDecimal.ONE) + .positionY(BigDecimal.valueOf(400))) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(31)) + .type("TEXT") + .label("Adresse personnelle") + .page(BigDecimal.ONE) + .positionY(BigDecimal.valueOf(410))) + .addFieldsItem( + new TemplateGetTemplateById200ResponseFieldsInner() + .id(BigDecimal.valueOf(32)) + .type("TEXT") + .label("Téléphones") + .page(BigDecimal.ONE) + .positionY(BigDecimal.valueOf(420)))); + when(documensoClientMock.useTemplate(any())) + .thenReturn( + new TemplateCreateDocumentFromTemplate200Response() + .id(BigDecimal.valueOf(998)) + .status(TemplateCreateDocumentFromTemplate200Response.StatusEnum.PENDING) + .title(templateTitle) + .addRecipientsItem( + new TemplateCreateDocumentFromTemplate200ResponseRecipientsInner() + .id(BigDecimal.valueOf(1)) + .email(monitor.getEmail()) + .name("Monitor") + .token("monitor-token"))); + + var toCreate = + new CrupdateDocumensoDocument().studentId(student.getId()).templateName(templateTitle); + + anApi(ADMIN1_TOKEN).generateDocumensoDocument(toCreate); + + var useTemplateCaptor = + org.mockito.ArgumentCaptor.forClass( + school.hei.haapi.service.documenso.gen.model.TemplateCreateDocumentFromTemplateRequest + .class); + verify(documensoClientMock).useTemplate(useTemplateCaptor.capture()); + var prefillByFieldId = + useTemplateCaptor.getValue().getPrefillFields().stream() + .collect( + java.util.stream.Collectors.toMap(f -> f.getId().longValue(), f -> f.getValue())); + + assertEquals(monitor.getFirstName() + " " + monitor.getLastName(), prefillByFieldId.get(20L)); + assertEquals(monitor.getAddress(), prefillByFieldId.get(21L)); + assertEquals(monitor.getPhone(), prefillByFieldId.get(22L)); + assertEquals(student.getFirstName() + " " + student.getLastName(), prefillByFieldId.get(30L)); + assertEquals(student.getAddress(), prefillByFieldId.get(31L)); + assertEquals(student.getPhone(), prefillByFieldId.get(32L)); + } + + @Test + void student_generate_document_ko() { + var toCreate = + new CrupdateDocumensoDocument().studentId(student.getId()).templateName(templateTitle); + + assertThrowsForbiddenException(() -> anApi(STUDENT1_TOKEN).generateDocumensoDocument(toCreate)); + } + + @Test + void webhook_completes_document_and_uploads_signed_pdf_to_s3() throws Exception { + var pendingDocument = + documensoDocumentRepository.save( + DocumensoDocument.builder() + .documensoDocumentId(4242L) + .template(template) + .student(student) + .level(StudentLevel.L1) + .status(school.hei.haapi.model.DocumensoDocumentStatus.PENDING) + .build()); + + var signedFile = File.createTempFile("signed", ".pdf"); + when(documensoClientMock.downloadSignedDocument(4242L)).thenReturn(signedFile); + when(bucketComponentMock.upload(any(), any())) + .thenReturn(new FileHash(FileHashAlgorithm.NONE, "dummy")); + + var response = + sendWebhook("{\"event\":\"DOCUMENT_COMPLETED\",\"payload\":{\"id\":4242}}", "dummy-secret"); + + assertEquals(200, response.statusCode()); + var updated = documensoDocumentRepository.findById(pendingDocument.getId()).orElseThrow(); + assertEquals(school.hei.haapi.model.DocumensoDocumentStatus.COMPLETED, updated.getStatus()); + assertNotNull(updated.getFileInfo()); + verify(bucketComponentMock).upload(eq(signedFile), any()); + } + + @Test + void webhook_with_wrong_secret_is_rejected() throws Exception { + var response = + sendWebhook("{\"event\":\"DOCUMENT_COMPLETED\",\"payload\":{\"id\":1}}", "wrong-secret"); + + assertEquals(401, response.statusCode()); + } + + private HttpResponse sendWebhook(String jsonBody, String secret) throws Exception { + var request = + HttpRequest.newBuilder() + .uri(URI.create("http://localhost:" + localPort + "/documenso/webhook")) + .header("Content-Type", "application/json") + .header("X-Documenso-Secret", secret) + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .build(); + return HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); + } +} diff --git a/src/test/java/school/hei/haapi/integration/FeeIT.java b/src/test/java/school/hei/haapi/integration/FeeIT.java index a9831d6f6..ed3866fa2 100644 --- a/src/test/java/school/hei/haapi/integration/FeeIT.java +++ b/src/test/java/school/hei/haapi/integration/FeeIT.java @@ -319,7 +319,7 @@ void manager_read_ok() throws ApiException { api.getFees(null, null, PAID, null, fee1().getCreationDatetime(), null, 1, 10, false, null); assertEquals(fee1(), actualFee); - assertEquals(3, actualFees2.getData().size()); + assertEquals(2, actualFees2.getData().size()); assertTrue(actualFees1.contains(fee1())); assertTrue(actualFees1.contains(fee2())); assertTrue(actualFees1.contains(fee3())); @@ -745,7 +745,7 @@ void manager_read_by_at_time_now() throws ApiException { var manager1Client = anApiClient(MANAGER1_TOKEN); var api = new PayingApi(manager1Client); var actualWorkFees = api.getFees(null, null, null, L1, null, null, 1, 10, false, null); - assertEquals(1, actualWorkFees.getData().size()); + assertEquals(0, actualWorkFees.getData().size()); } @Test