Skip to content

Commit

Permalink
Merge pull request #37 from consiglionazionaledellericerche/17-aggiun…
Browse files Browse the repository at this point in the history
…gere-documentazione-endopoint-rest-tramite-swagger

17 aggiungere documentazione endopoint rest tramite swagger
  • Loading branch information
criluc authored Mar 20, 2024
2 parents 2f8d5b6 + 68f2f44 commit 55158fc
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 47 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ nelle tabelle di frontiera Oracle di UGov Pj UWeb Timesheet Intime.
UGov PJ UWeb Timesheet prevede la possibilità di inserire in una tabella di frontiera *Oracle* le
informazioni relative a tempo a lavoro e assenze che vengono prelevate via REST da un server ePAS.

## Configurazione di ePAS per il mapping delle tipologie di assenza

UGov Pj Timesheet prevede di raggruppare le assenze in alcune tipologie, per esempio:

- *F* - FERIE MALATTIE E ROL
- *T* - Missione / Trasferta
- *A* - PARTECIPAZIONE ALLE ASSEMBLEE DEL PERSONALE
- *R* - RIUNIONI RSU
- *S* - ATTIVITà SVOLTE COME RLS

è necessario definire una correlazione tra questi codici ed i codici si assenza presenti in ePAS,
per questo motivo in ePAS è stata aggiunta la possibilità per ogni codice di assenza di specificare
un campo **External Id**, questo campo deve essere valorizzata per i tipi di assenza che si vogliono
esportare verso il sistema Cineca con il rispettivo codice Cineca.
Per esempio i codici ePAS *32*, *31*, *94* dovranno aver valorizzato il campo *External Id* con il valore *F*.

## Configurazione e avvio del servizio tramite docker/docker-compose

ePAS UGov Pj Timesheet può essere facilmente installato via docker-compose su server Linux
Expand Down Expand Up @@ -71,7 +87,7 @@ Gli endpoint REST sono protetti tramite Basic Auth, con utente e password config
le application.properties del servizio, oppure tramite le variabili d'ambiente
*spring.security.username*, *spring.security.password* nel caso di avvio tramite docker/docker-compose.

## Visualizzazione task schedulati
## Task schedulati ogni giorno e loro consultazione

È possibile visualizzare le informazioni dei task cron schedulati per la sincronizzazone
delle informazioni tra ePAS e le tabelle Oracle di UGOV PJ UWeb Timesheet.
Expand All @@ -80,6 +96,12 @@ L'endpoint da consultare è **/actuator/scheduledtasks**.

**Il servizio effettua l'aggiornamento dei dati di presenze ed assenze di tutto il personale oggi mattina alle 05:30 AM.**

## OpenAPI ed interfaccia Swagger integrata

Il servizio espone le informazioni sulle proprie API REST in formato *OpenApi* all'indirizzo **/v3/api-docs**.
Inoltre è presente un'interfaccia web *Swagger UI* con la descrizione di tutti i servizi REST e la possibilità
di effettuare le chiamate REST, l'interfaccia è raggiungibile all'indirizzo **/swagger-ui/index.html**.

## Metriche del servizio

Il servizio esporta alcune metriche in formato Prometheus, è possibile consultarle all'endpoint
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.1
0.1.0
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<spring-cloud.version>2023.0.0</spring-cloud.version>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<jib.maven-plugin.version>3.4.0</jib.maven-plugin.version>
<springdoc-openapi.version>2.4.0</springdoc-openapi.version>
<hypersistence-utils.version>3.7.3</hypersistence-utils.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -51,7 +53,12 @@
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-63</artifactId>
<version>3.7.3</version>
<version>${hypersistence-utils.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 Consiglio Nazionale delle Ricerche
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package it.cnr.iit.epas.timesheet.ugovpj.config;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.annotations.security.SecuritySchemes;
import io.swagger.v3.oas.annotations.servers.Server;
import org.springframework.context.annotation.Configuration;

/**
* Configurazione dei parametri generali della documentazione tramite OpenAPI.
*
* @author Cristian Lucchesi
*/
@Configuration
@OpenAPIDefinition(
info = @Info(title = "ePAS UGov PJ Timesheet",
version = "0.1.0",
description = "ePAS UGov PJ Timesheet è il servizio per l'integrazione dei dati delle presenze/assenze presenti"
+ "in ePAS con le tabelle di frontiera Oracle di UGov PJ UWeb Timesheet Intime di Cineca"),
servers = {
@Server(url = "/", description = "ePAS UGov PJ Timesheet URL")}
)
@SecuritySchemes(value = {
@SecurityScheme(
name = OpenApiConfiguration.BASIC_AUTHENTICATION,
type = SecuritySchemeType.HTTP,
in = SecuritySchemeIn.HEADER,
scheme = "basic")
})
public class OpenApiConfiguration {

public static final String BASIC_AUTHENTICATION = "Basic Authentication";

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,20 @@ public interface PersonTimeDetailRepo
@Query(value = "DELETE FROM PersonTimeDetail ptd")
void truncateTable();

@Procedure(procedureName = "IE_PJ.P_CARICA_MARCATURE")
int loadDetails();
@Procedure(procedureName = "P_CARICA_MARCATURE")
void loadDetails();

@Procedure(procedureName = "P_CARICA_MARCATURE_JOB")
void loadDetailsJob();

@Transactional
@Modifying
@Query(value = "call IE_PJ.P_CARICA_MARCATURE()", nativeQuery = true)
void loadDetailsNative();

@Transactional
@Modifying
@Query(value = "call P_CARICA_MARCATURE_JOB", nativeQuery = true)
void loadDetailsJobNative();

@Procedure(procedureName = "IE_PJ.P_CARICA_MARCATURE_JOB")
int loadDetailsJob();
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@
*
* @author Cristian Lucchesi
*/
/**
*
*/
@Slf4j
@RequiredArgsConstructor
@Service
Expand Down Expand Up @@ -229,6 +226,15 @@ public void loadDetails() {
repo.loadDetailsJob();
}

/**
* Invoca le stored procedure per il caricamento dei dati delle marcature
* nella tabella definitiva utilizzando native query.
*/
public void loadDetailsNative() {
repo.loadDetailsNative();
repo.loadDetailsJobNative();
}

/**
* Genera un ID univoco per le righe di marcatura di un dipendente di un
* giorno. Per ogni absenceGroup viene generato un ID basato sul suo hashcode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,25 @@
*/
package it.cnr.iit.epas.timesheet.ugovpj.v1.controller;

import it.cnr.iit.epas.timesheet.ugovpj.client.EpasClient;
import it.cnr.iit.epas.timesheet.ugovpj.client.dto.OfficeDto;
import it.cnr.iit.epas.timesheet.ugovpj.client.dto.PersonMonthRecapDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import it.cnr.iit.epas.timesheet.ugovpj.service.CachingService;
import it.cnr.iit.epas.timesheet.ugovpj.service.SyncService;
import it.cnr.iit.epas.timesheet.ugovpj.v1.ApiRoutes;
import it.cnr.iit.epas.timesheet.ugovpj.v1.dto.PersonTimeDetailDto;
import it.cnr.iit.epas.timesheet.ugovpj.v1.dto.PersonTimeDetailMapper;

import java.time.LocalDate;
import java.time.YearMonth;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -47,69 +45,104 @@
*
* @author Cristian Lucchesi
*/
@Tag(
name = "Admin Controller",
description = "Avvio delle operazioni di sincronizzazione e altre funzioni di amministrazione del servizio")
@Slf4j
@RequestMapping(ApiRoutes.BASE_PATH + "/admin")
@RestController
@RequiredArgsConstructor
public class AdminController {

private final EpasClient epasClient;
private final SyncService syncService;
private final CachingService cachingService;
private final PersonTimeDetailMapper mapper;

@GetMapping("/offices")
public ResponseEntity<List<OfficeDto>> offices(
@RequestParam("atDate")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
Optional<LocalDate> atDate) {
log.debug("Ricevuta richiesta estrazione lista di uffici da ePAS alla data {}", atDate.orElse(LocalDate.now()));
val offices = epasClient.getActiveOffices(atDate.orElse(LocalDate.now()));
return ResponseEntity.ok().body(offices);
}

@GetMapping("/officeMonthRecap")
public ResponseEntity<List<PersonMonthRecapDto>> officeMonthRecap(
@RequestParam("officeId") Long officeId, @RequestParam("year") int year, @RequestParam("month") int month) {
log.debug("Ricevuta richiesta visualizzazione riepilogo mensile per ufficio id={} {}/{}",
officeId, month, year);
val monthRecap = epasClient.getMonthRecap(officeId, year, month);
return ResponseEntity.ok().body(monthRecap);
}

@PostMapping("/officeMonthRecap")
public ResponseEntity<List<PersonTimeDetailDto>> syncOfficeMonthRecap(
@RequestParam("officeId") Long officeId, @RequestParam("year") int year, @RequestParam("month") int month) {
log.info("Ricevuta richiesta aggiornamento del riepilogo mensile per ufficio id={} {}/{}", officeId, month, year);
val details = syncService.syncOfficeMonth(officeId, YearMonth.of(year, month), Optional.empty());
return ResponseEntity.ok().body(details.stream().map(mapper::convert).collect(Collectors.toList()));
}

@Operation(
summary = "Avvio della sincronizzazione dei dati di tutti gli uffici.",
description = "La sincronizzazione non svuota la tabella IE_PJ_MARCATURE, eventualmente svuotarla con l'apposito"
+ "endpoint REST. Il periodo dipende dalla configurazione.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Dati sincronizzati e restituito il numero di riepiloghi inseriti")
})
@PostMapping("/syncAll")
public ResponseEntity<Integer> syncAll() {
log.info("Ricevuta richiesta aggiornamento dei riepilogi di tutti gli uffici");
val details = syncService.syncAll();
return ResponseEntity.ok().body(details.size());
}

@Operation(
summary = "Cancellata tutti i dati delle presenze/assenza, svuotando la tabella IE_PJ_MARCATURE.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Dati cancellati correttamente")
})
@DeleteMapping("/deleteAll")
public ResponseEntity<Void> deleteAll() {
log.debug("Richiesta eliminazione di tutti i dettagli del tempo a lavoro e assenze personale");
syncService.deleteAllPersonTimeDetails();
return ResponseEntity.ok().build();
}

@Operation(
summary = "Effettua il caricamento definitivo dei dati delle presenze/assenza, invocando le apposite "
+ "stored procedures.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Stored procedure di caricamento definitivo dei dati invocate correttamente")
})
@PostMapping("/loadDetails")
public ResponseEntity<Void> loadDetails() {
log.debug("Richiesta caricamento definitivo dati tramite stored procedure");
syncService.loadDetails();
return ResponseEntity.ok().build();
}

@Operation(
summary = "Effettua il caricamento definitivo dei dati delle presenze/assenza, invocando le apposite "
+ "stored procedures (in modo SQL nativo, senza utilizzare Spring Data).")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Stored procedure di caricamento definitivo dei dati invocate correttamente")
})
@PostMapping("/loadDetailsNative")
public ResponseEntity<Void> loadDetailsNative() {
log.debug("Richiesta caricamento definitivo dati tramite stored procedure (native)");
syncService.loadDetailsNative();
return ResponseEntity.ok().build();
}

@Operation(
summary = "Effettua la sincronizzazione dei dati di presenza/assenza di un singolo ufficio "
+ "in un singolo mese.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Inseriti i dati di presenze/assenza e mostrato il dettaglio dei dati inseriti"),
@ApiResponse(responseCode = "400",
description = "Non passato uno dei tre parametri obbligatori: officeId, year, month.",
content = @Content)
})
@PostMapping("/officeMonthRecap")
public ResponseEntity<List<PersonTimeDetailDto>> syncOfficeMonthRecap(
@RequestParam("officeId") Long officeId, @RequestParam("year") int year, @RequestParam("month") int month) {
log.info("Ricevuta richiesta aggiornamento del riepilogo mensile per ufficio id={} {}/{}", officeId, month, year);
val details = syncService.syncOfficeMonth(officeId, YearMonth.of(year, month), Optional.empty());
return ResponseEntity.ok().body(details.stream().map(mapper::convert).collect(Collectors.toList()));
}

@Operation(
summary = "Svuota le cache utilizzate, in particolare quella dei tipi di marcatura.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Cache svuotata correttamente")
})
@DeleteMapping("/evictCaches")
public ResponseEntity<Void> evictCaches() {
log.debug("Richiesta evict di tutte le cache");
cachingService.evictAllCaches();
return ResponseEntity.ok().build();
}

}
Loading

0 comments on commit 55158fc

Please sign in to comment.