From 0a056334eeebb5e6dc5e46ab248326451fba7a11 Mon Sep 17 00:00:00 2001 From: Celestino Bellone <3385346+cbellone@users.noreply.github.com> Date: Fri, 3 Feb 2023 19:20:19 +0100 Subject: [PATCH] API to retrieve check-in log --- .../api/v1/admin/EventApiV1Controller.java | 13 +++++ .../java/alfio/manager/CheckInManager.java | 8 +++ .../model/api/v1/admin/CheckInLogEntry.java | 52 +++++++++++++++++++ .../java/alfio/model/audit/ScanAudit.java | 26 ++++++---- .../repository/audit/ScanAuditRepository.java | 9 ++++ .../reservation/BaseReservationFlowTest.java | 8 ++- .../ReservationFlowIntegrationTest.java | 18 ++++++- 7 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java diff --git a/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java b/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java index 4580beacb0..5fe8997384 100644 --- a/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java +++ b/src/main/java/alfio/controller/api/v1/admin/EventApiV1Controller.java @@ -25,6 +25,7 @@ import alfio.manager.user.UserManager; import alfio.model.*; import alfio.model.ExtensionSupport.ExtensionMetadataValue; +import alfio.model.api.v1.admin.CheckInLogEntry; import alfio.model.api.v1.admin.EventCreationRequest; import alfio.model.api.v1.admin.LinkedSubscriptions; import alfio.model.group.Group; @@ -76,6 +77,7 @@ public class EventApiV1Controller { private final ExtensionRepository extensionRepository; private final ConfigurationManager configurationManager; private final AdminJobManager adminJobManager; + private final CheckInManager checkInManager; @PostMapping("/create") @Transactional @@ -199,6 +201,17 @@ public ResponseEntity generateTicketsForSubscribers(@PathVariable("slug })); } + @GetMapping("/{slug}/check-in-log") + public ResponseEntity> checkInLog(@PathVariable("slug") String slug, + Principal user) { + try { + return ResponseEntity.ok(checkInManager.retrieveLogEntries(slug, user.getName())); + } catch (Exception ex) { + log.error("Error while loading check-in log entries", ex); + return ResponseEntity.internalServerError().build(); + } + } + private LinkedSubscriptions retrieveLinkedSubscriptionsForEvent(String slug, int id, int organizationId) { var subscriptionIds = eventManager.getLinkedSubscriptionIds(id, organizationId); return new LinkedSubscriptions(slug, subscriptionIds); diff --git a/src/main/java/alfio/manager/CheckInManager.java b/src/main/java/alfio/manager/CheckInManager.java index a160a79410..9beaac993f 100644 --- a/src/main/java/alfio/manager/CheckInManager.java +++ b/src/main/java/alfio/manager/CheckInManager.java @@ -20,6 +20,7 @@ import alfio.manager.system.ConfigurationManager; import alfio.model.*; import alfio.model.Ticket.TicketStatus; +import alfio.model.api.v1.admin.CheckInLogEntry; import alfio.model.audit.ScanAudit; import alfio.model.checkin.AttendeeSearchResults; import alfio.model.decorator.TicketPriceContainer; @@ -533,6 +534,13 @@ public CheckInStatistics getStatistics(String eventName, String username) { .orElse(null); } + public List retrieveLogEntries(String eventName, String username) { + return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName) + .filter(EventManager.checkOwnership(username, organizationRepository)) + .map(event -> scanAuditRepository.loadEntries(event.getId())) + .orElse(List.of()); + } + private boolean areStatsEnabled(EventAndOrganizationId event) { return configurationManager.getFor(CHECK_IN_STATS, event.getConfigurationLevel()).getValueAsBooleanOrDefault(); } diff --git a/src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java b/src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java new file mode 100644 index 0000000000..85100e34df --- /dev/null +++ b/src/main/java/alfio/model/api/v1/admin/CheckInLogEntry.java @@ -0,0 +1,52 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see . + */ +package alfio.model.api.v1.admin; + +import alfio.model.audit.ScanAudit; +import alfio.model.modification.AttendeeData; +import alfio.model.support.JSONData; +import alfio.util.Json; +import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.List; + +public class CheckInLogEntry { + private final String ticketId; + private final AttendeeData attendeeData; + private final List audit; + + public CheckInLogEntry(@Column("t_uuid") String ticketId, + @Column("attendee_data") @JSONData AttendeeData attendeeData, + @Column("scans") String scansAsString) { + this.ticketId = ticketId; + this.attendeeData = attendeeData; + this.audit = Json.fromJson(scansAsString, new TypeReference<>() {}); + } + + public String getTicketId() { + return ticketId; + } + + public AttendeeData getAttendeeData() { + return attendeeData; + } + + public List getAudit() { + return audit; + } +} diff --git a/src/main/java/alfio/model/audit/ScanAudit.java b/src/main/java/alfio/model/audit/ScanAudit.java index b21640bc66..bded180dd9 100644 --- a/src/main/java/alfio/model/audit/ScanAudit.java +++ b/src/main/java/alfio/model/audit/ScanAudit.java @@ -18,9 +18,12 @@ import alfio.manager.support.CheckInStatus; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import java.time.ZonedDateTime; +import java.time.LocalDateTime; @Getter public class ScanAudit { @@ -31,23 +34,26 @@ public enum Operation { } private final String ticketUuid; - private final int eventId; - private final ZonedDateTime scanTimestamp; + private final LocalDateTime scanTimestamp; private final String username; private final CheckInStatus checkInStatus; private final Operation operation; - public ScanAudit(@Column("ticket_uuid") String ticketUuid, - @Column("event_id_fk") int eventId, - @Column("scan_ts") ZonedDateTime scanTimestamp, - @Column("username") String username, - @Column("check_in_status") CheckInStatus checkInStatus, - @Column("operation") Operation operation) { + @JsonCreator + public ScanAudit(@JsonProperty("ticketUuid") @Column("ticket_uuid") String ticketUuid, + @JsonProperty("scanTimestamp") @Column("scan_ts") LocalDateTime scanTimestamp, + @JsonProperty("username") @Column("username") String username, + @JsonProperty("checkInStatus") @Column("check_in_status") CheckInStatus checkInStatus, + @JsonProperty("operation") @Column("operation") Operation operation) { this.ticketUuid = ticketUuid; - this.eventId = eventId; this.scanTimestamp = scanTimestamp; this.username = username; this.checkInStatus = checkInStatus; this.operation = operation; } + + @JsonIgnore + public String getTicketUuid() { + return ticketUuid; + } } diff --git a/src/main/java/alfio/repository/audit/ScanAuditRepository.java b/src/main/java/alfio/repository/audit/ScanAuditRepository.java index eca3813687..fce7a42d31 100644 --- a/src/main/java/alfio/repository/audit/ScanAuditRepository.java +++ b/src/main/java/alfio/repository/audit/ScanAuditRepository.java @@ -17,6 +17,7 @@ package alfio.repository.audit; import alfio.manager.support.CheckInStatus; +import alfio.model.api.v1.admin.CheckInLogEntry; import alfio.model.audit.ScanAudit; import ch.digitalfondue.npjt.Bind; import ch.digitalfondue.npjt.Query; @@ -38,4 +39,12 @@ Integer insert(@Bind("ticketUuid") String ticketUuid, @Query("select * from scan_audit where event_id_fk = :eventId") List findAllForEvent(@Bind("eventId") int eventId); + @Query("select t.uuid t_uuid, jsonb_build_object('firstName', t.first_name, 'lastName', t.last_name, 'email', t.email_address, 'metadata', coalesce(t.metadata::jsonb#>'{metadataMap, general, attributes}', '{}')) attendee_data, jsonb_agg(jsonb_build_object('ticketUuid', sa.ticket_uuid, 'scanTimestamp', sa.scan_ts, 'username', sa.username, 'checkInStatus', sa.check_in_status, 'operation', sa.operation)) scans from ticket t\n" + + " join event e on t.event_id = e.id" + + " join scan_audit sa on e.id = sa.event_id_fk and t.uuid = sa.ticket_uuid" + + " where e.id = :eventId" + + " and t.status = 'CHECKED_IN'" + + " group by 1,2") + List loadEntries(@Bind("eventId") int eventId); + } \ No newline at end of file diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java index 8bf3a4755c..26cf51b38c 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java @@ -993,7 +993,7 @@ protected void testBasicFlow(Supplier contextSupplier) t assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus()); CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode(); tc.setCode(ticketCode); - assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken("ciccio", "ciccio")).getResult().getStatus()); + assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken(context.userId + "_api", "")).getResult().getStatus()); List audits = scanAuditRepository.findAllForEvent(context.event.getId()); assertFalse(audits.isEmpty()); assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier))); @@ -1001,7 +1001,7 @@ protected void testBasicFlow(Supplier contextSupplier) t extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); assertEventLogged(extLogs, TICKET_CHECKED_IN, 2); - + validateCheckInData(context); TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode); assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus()); @@ -1191,6 +1191,10 @@ protected void testBasicFlow(Supplier contextSupplier) t } + protected void validateCheckInData(ReservationFlowContext context) { + + } + protected void performAndValidatePayment(ReservationFlowContext context, String reservationId, int promoCodeId, diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java index 4e4b0af64f..08dee1c054 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/ReservationFlowIntegrationTest.java @@ -62,6 +62,7 @@ import java.util.List; import static alfio.test.util.IntegrationTestUtil.*; +import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class}) @@ -71,6 +72,7 @@ class ReservationFlowIntegrationTest extends BaseReservationFlowTest { private final OrganizationRepository organizationRepository; private final UserManager userManager; + private final CheckInManager checkInManager; @Autowired public ReservationFlowIntegrationTest(OrganizationRepository organizationRepository, @@ -109,7 +111,8 @@ public ReservationFlowIntegrationTest(OrganizationRepository organizationReposit UserRepository userRepository, OrganizationDeleter organizationDeleter, PromoCodeDiscountRepository promoCodeDiscountRepository, - PromoCodeRequestManager promoCodeRequestManager) { + PromoCodeRequestManager promoCodeRequestManager, + CheckInManager checkInManager) { super(configurationRepository, eventManager, eventRepository, @@ -147,6 +150,7 @@ public ReservationFlowIntegrationTest(OrganizationRepository organizationReposit promoCodeRequestManager); this.organizationRepository = organizationRepository; this.userManager = userManager; + this.checkInManager = checkInManager; } private ReservationFlowContext createContext() { @@ -174,4 +178,16 @@ protected void performAdditionalTests(ReservationFlowContext context) { var event = context.event; BaseIntegrationTest.testTransferEventToAnotherOrg(event.getId(), event.getOrganizationId(), context.userId, jdbcTemplate); } + + @Override + protected void validateCheckInData(ReservationFlowContext context) { + var entries = checkInManager.retrieveLogEntries(context.event.getShortName(), context.userId); + assertEquals(1, entries.size()); + var entry = entries.get(0); + assertNotNull(entry.getTicketId()); + assertNotNull(entry.getAttendeeData()); + assertNotNull(entry.getAttendeeData().getMetadata()); + assertNotNull(entry.getAudit()); + entry.getAudit().forEach(audit -> assertEquals(context.userId + "_api", audit.getUsername())); + } }