Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link subscriptions with categories #1361

Merged
merged 2 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@
import alfio.model.PromoCodeDiscount;
import alfio.model.api.v1.admin.CheckInLogEntry;
import alfio.model.api.v1.admin.EventCreationRequest;
import alfio.model.api.v1.admin.LinkedSubscriptions;
import alfio.model.api.v1.admin.LinkedSubscription;
import alfio.model.group.Group;
import alfio.model.modification.EventModification;
import alfio.model.modification.LinkedGroupModification;
import alfio.model.modification.TicketCategoryModification;
import alfio.model.result.ErrorCode;
import alfio.model.result.Result;
import alfio.model.result.ValidationResult;
import alfio.model.subscription.EventSubscriptionLink;
import alfio.model.subscription.LinkSubscriptionsToEventRequest;
import alfio.model.system.ConfigurationKeys;
import alfio.model.user.Organization;
import alfio.repository.ExtensionRepository;
Expand Down Expand Up @@ -184,15 +186,15 @@ public ResponseEntity<String> update(@PathVariable String slug, @RequestBody Eve
}

@GetMapping("/{slug}/subscriptions")
public ResponseEntity<LinkedSubscriptions> getLinkedSubscriptions(@PathVariable String slug, Principal user) {
public ResponseEntity<LinkedSubscription> getLinkedSubscriptions(@PathVariable String slug, Principal user) {
var event = accessService.checkEventOwnership(user, slug);
return ResponseEntity.ok(retrieveLinkedSubscriptionsForEvent(slug, event.getId(), event.getOrganizationId()));
}

@PutMapping("/{slug}/subscriptions")
public ResponseEntity<LinkedSubscriptions> updateLinkedSubscriptions(@PathVariable String slug,
@RequestBody List<UUID> subscriptions,
Principal user) {
public ResponseEntity<LinkedSubscription> updateLinkedSubscriptions(@PathVariable String slug,
@RequestBody List<LinkSubscriptionsToEventRequest> subscriptions,
Principal user) {
var eventAndOrgId = accessService.checkDescriptorsLinkRequest(user, slug, subscriptions);
eventManager.updateLinkedSubscriptions(subscriptions, eventAndOrgId.getId(), eventAndOrgId.getOrganizationId());
return ResponseEntity.ok(retrieveLinkedSubscriptionsForEvent(slug, eventAndOrgId.getId(), eventAndOrgId.getOrganizationId()));
Expand Down Expand Up @@ -222,9 +224,11 @@ public ResponseEntity<List<CheckInLogEntry>> checkInLog(@PathVariable String slu
}
}

private LinkedSubscriptions retrieveLinkedSubscriptionsForEvent(String slug, int id, int organizationId) {
var subscriptionIds = eventManager.getLinkedSubscriptionIds(id, organizationId);
return new LinkedSubscriptions(slug, subscriptionIds);
private LinkedSubscription retrieveLinkedSubscriptionsForEvent(String slug, int id, int organizationId) {
var subscriptions = eventManager.getLinkedSubscriptions(id, organizationId);
return new LinkedSubscription(slug,
subscriptions.stream().collect(Collectors.toMap(EventSubscriptionLink::getSubscriptionDescriptorId, EventSubscriptionLink::getCompatibleCategories))
);
}

private Optional<Event> updateEvent(String slug, EventCreationRequest request, Principal user, String imageRef) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
import alfio.controller.api.admin.SubscriptionApiController;
import alfio.manager.*;
import alfio.manager.user.UserManager;
import alfio.model.api.v1.admin.LinkedEvent;
import alfio.model.api.v1.admin.SubscriptionDescriptorModificationRequest;
import alfio.model.modification.SubscriptionDescriptorModification;
import alfio.model.subscription.EventSubscriptionLink;
import alfio.model.subscription.LinkEventsToSubscriptionRequest;
import alfio.model.subscription.SubscriptionDescriptorWithStatistics;
import alfio.util.Json;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -36,7 +39,7 @@
import java.util.List;
import java.util.UUID;

import static alfio.controller.api.admin.SubscriptionApiController.loadLinkedEvents;
import static java.util.stream.Collectors.toList;

@RestController
@RequestMapping("/api/v1/admin/subscription")
Expand All @@ -47,22 +50,19 @@ public class SubscriptionApiV1Controller {
private final FileUploadManager fileUploadManager;
private final FileDownloadManager fileDownloadManager;
private final UserManager userManager;
private final EventManager eventManager;
private final AccessService accessService;
private final PurchaseContextFieldManager purchaseContextFieldManager;

public SubscriptionApiV1Controller(SubscriptionManager subscriptionManager,
FileUploadManager fileUploadManager,
FileDownloadManager fileDownloadManager,
UserManager userManager,
EventManager eventManager,
AccessService accessService,
PurchaseContextFieldManager purchaseContextFieldManager) {
this.subscriptionManager = subscriptionManager;
this.fileUploadManager = fileUploadManager;
this.fileDownloadManager = fileDownloadManager;
this.userManager = userManager;
this.eventManager = eventManager;
this.accessService = accessService;
this.purchaseContextFieldManager = purchaseContextFieldManager;
}
Expand Down Expand Up @@ -121,31 +121,26 @@ public ResponseEntity<SubscriptionDescriptorWithStatistics> get(@PathVariable UU
}

@GetMapping("/{subscriptionId}/events")
public ResponseEntity<List<String>> getLinkedEvents(@PathVariable UUID subscriptionId,
Principal principal) {
public ResponseEntity<List<LinkedEvent>> getLinkedEvents(@PathVariable UUID subscriptionId,
Principal principal) {
accessService.checkSubscriptionDescriptorOwnership(principal, subscriptionId.toString());
var organization = userManager.findUserOrganizations(principal.getName()).get(0);
return ResponseEntity.ok(loadLinkedEvents(subscriptionManager.getLinkedEvents(organization.getId(), subscriptionId)));
return ResponseEntity.ok(toLinkedEvents(subscriptionManager.getLinkedEvents(organization.getId(), subscriptionId)));
}

@PostMapping("/{subscriptionId}/events")
public ResponseEntity<List<String>> updateLinkedEvents(@PathVariable UUID subscriptionId,
@RequestBody List<String> eventSlugs,
Principal principal) {
if (eventSlugs == null) {
public ResponseEntity<List<LinkedEvent>> updateLinkedEvents(@PathVariable UUID subscriptionId,
@RequestBody List<LinkEventsToSubscriptionRequest> linkedEvents,
Principal principal) {
if (linkedEvents == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
accessService.checkEventLinkRequest(principal, subscriptionId.toString(), eventSlugs);
accessService.checkEventLinkRequest(principal, subscriptionId.toString(), linkedEvents);
var organization = userManager.findUserOrganizations(principal.getName()).get(0);
int organizationId = organization.getId();

List<Integer> eventIds = List.of();
if (!eventSlugs.isEmpty()) {
eventIds = eventManager.getEventIdsBySlug(eventSlugs, organizationId);
}
var result = subscriptionManager.updateLinkedEvents(organizationId, subscriptionId, eventIds);
var result = subscriptionManager.updateLinkedEvents(organizationId, subscriptionId, linkedEvents);
if (result.isSuccess()) {
return ResponseEntity.ok(loadLinkedEvents(result.getData()));
return ResponseEntity.ok(toLinkedEvents(result.getData()));
} else {
if (log.isWarnEnabled()) {
log.warn("Cannot update linked events {}", Json.toJson(result.getErrors()));
Expand All @@ -170,4 +165,10 @@ private String fetchImage(String url) {
return null;
}
}

private static List<LinkedEvent> toLinkedEvents(List<EventSubscriptionLink> links) {
return links.stream()
.map(l -> new LinkedEvent(l.getEventShortName(), l.getCompatibleCategories()))
.collect(toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import alfio.repository.SubscriptionRepository;
import alfio.repository.TicketCategoryRepository;
import alfio.util.ClockProvider;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
Expand All @@ -41,7 +41,6 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -108,13 +107,12 @@ public String process(AdminJobSchedule schedule) {
.getValueAsBooleanOrDefault();
if (generationEnabled) {
var subscriptions = subscriptionsByEvent.get(event.getId());
var optionalCategory = ticketCategoryRepository.findFirstWithAvailableTickets(event.getId());
if (optionalCategory.isPresent()) {
var category = optionalCategory.get();
var availableCategories = ticketCategoryRepository.findAllWithAvailableTickets(event.getId());
if (CollectionUtils.isNotEmpty(availableCategories)) {
// 3. create reservation import request for the subscribers. ID is "AUTO_${eventShortName}_${now_ISO}"
var requestId = String.format("AUTO_%s_%s", event.getShortName(), LocalDateTime.now(clockProvider.getClock()).format(DateTimeFormatter.ISO_DATE_TIME));
requestManager.insertRequest(requestId,
buildBody(event, subscriptions, category, fieldsByEventId.getOrDefault(event.getId(), Set.of())),
buildBody(event, subscriptions, availableCategories, fieldsByEventId.getOrDefault(event.getId(), Set.of())),
event,
false,
"admin");
Expand All @@ -132,18 +130,34 @@ public String process(AdminJobSchedule schedule) {

private AdminReservationModification buildBody(Event event,
List<AvailableSubscriptionsByEvent> subscriptions,
TicketCategory category,
List<TicketCategory> availableCategories,
Set<String> fieldsForEvent) {
var clock = clockProvider.getClock();
var subscriptionsByDescriptor = subscriptions.stream().collect(Collectors.groupingBy(AvailableSubscriptionsByEvent::getDescriptorId));
var tickets = subscriptionsByDescriptor.values().stream().flatMap(availableSubscriptionsByEvents -> {
var firstValue = availableSubscriptionsByEvents.get(0);
var categoryOptional = availableCategories.stream()
.filter(c -> CollectionUtils.isEmpty(firstValue.getCompatibleCategoryIds()) || firstValue.getCompatibleCategoryIds().contains(c.getId()))
.findFirst();

if (categoryOptional.isEmpty()) {
var categoriesIds = availableCategories.stream().map(TicketCategory::getId).collect(Collectors.toSet());
log.warn("Skipping descriptor {}. No compatible category found (wanted: one of {}, available: {})", firstValue.getDescriptorId(), firstValue.getCompatibleCategoryIds(), categoriesIds);
}

return categoryOptional.stream()
.map(category -> new TicketsInfo(
new Category(category.getId(), category.getName(), category.getPrice(), category.getTicketAccessType()),
toAttendees(availableSubscriptionsByEvents, fieldsForEvent),
false,
false
));
}).collect(Collectors.toList());

return new AdminReservationModification(
new DateTimeModification(LocalDate.now(clock), LocalTime.now(clock).plus(5L, ChronoUnit.MINUTES)),
new DateTimeModification(LocalDate.now(clock), LocalTime.now(clock).plusMinutes(5L)),
new CustomerData("", "", "", null, "", null, null, null, null),
List.of(new TicketsInfo(
new Category(category.getId(), category.getName(), category.getPrice(), category.getTicketAccessType()),
toAttendees(subscriptions, fieldsForEvent),
false,
false
)),
tickets,
event.getContentLanguages().get(0).getLanguage(),
false,
false,
Expand Down
30 changes: 25 additions & 5 deletions src/main/java/alfio/manager/AccessService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import alfio.model.modification.GroupModification;
import alfio.model.modification.PromoCodeDiscountModification;
import alfio.model.modification.ReservationRequest;
import alfio.model.subscription.LinkEventsToSubscriptionRequest;
import alfio.model.subscription.LinkSubscriptionsToEventRequest;
import alfio.model.user.Organization;
import alfio.model.user.Role;
import alfio.repository.*;
Expand Down Expand Up @@ -371,16 +373,24 @@ public void checkPurchaseContextOwnership(Principal principal,
}
}

public EventAndOrganizationId checkDescriptorsLinkRequest(Principal principal, String eventSlug, List<UUID> descriptorsToLink) {
public EventAndOrganizationId checkDescriptorsLinkRequest(Principal principal, String eventSlug, List<LinkSubscriptionsToEventRequest> descriptorsToLink) {
var event = checkEventOwnership(principal, eventSlug);
if (descriptorsToLink.isEmpty()) {
// user is requesting to remove all subscriptions from event
return event;
}
var count = subscriptionRepository.countDescriptorsBelongingToOrganization(descriptorsToLink, event.getOrganizationId());
var descriptorsId = descriptorsToLink.stream().map(LinkSubscriptionsToEventRequest::getDescriptorId).collect(Collectors.toList());
var count = subscriptionRepository.countDescriptorsBelongingToOrganization(descriptorsId, event.getOrganizationId());
if (count == null || descriptorsToLink.size() != count) {
throw new AccessDeniedException();
}
var categoriesToLink = descriptorsToLink.stream().flatMap(sl -> sl.getCategories().stream()).collect(Collectors.toSet());
if (!categoriesToLink.isEmpty()) {
count = ticketCategoryRepository.countCategoryForEvent(categoriesToLink, event.getId());
if (categoriesToLink.size() != count) {
throw new AccessDeniedException();
}
}
return event;
}
public void checkSubscriptionDescriptorOwnership(Principal principal, String publicIdentifier) {
Expand Down Expand Up @@ -480,13 +490,23 @@ public int checkAccessToPromoCodeEventOrganization(Principal principal, Integer
}
}

public void checkEventLinkRequest(Principal principal, String subscriptionId, List<String> eventSlugs) {
public void checkEventLinkRequest(Principal principal, String subscriptionId, List<LinkEventsToSubscriptionRequest> linkRequests) {
int organizationId = subscriptionRepository.findOrganizationIdForDescriptor(UUID.fromString(subscriptionId))
.orElseThrow(AccessDeniedException::new);
checkOrganizationOwnership(principal, organizationId);
if (eventSlugs.size() > 0 && eventSlugs.size() != eventRepository.countEventsInOrganization(organizationId, eventSlugs)) {
var eventSlugs = linkRequests.stream().map(LinkEventsToSubscriptionRequest::getSlug).collect(Collectors.toSet());
if (!eventSlugs.isEmpty() && eventSlugs.size() != eventRepository.countEventsInOrganization(organizationId, eventSlugs)) {
throw new AccessDeniedException();
}
linkRequests.forEach(request -> {
var categoriesToLink = request.getCategories();
if (!categoriesToLink.isEmpty()) {
int count = ticketCategoryRepository.countCategoryForEvent(Set.copyOf(request.getCategories()), request.getSlug());
if (categoriesToLink.size() != count) {
throw new AccessDeniedException();
}
}
});
}

public EventAndOrganizationId canAccessEvent(Principal principal, String eventShortName) {
Expand Down Expand Up @@ -555,7 +575,7 @@ public void checkEventAndReservationOwnership(Principal principal, String eventN
if (reservationIds.size() != reservationRepository.countReservationsWithEventId(reservationIds, eventAndOrgId.getId())) {
log.warn("Some reservation ids {} are not in the event {}", reservationIds, eventName);
throw new AccessDeniedException();
};
}
}

public void checkEventAndReservationAndTransactionOwnership(Principal principal, String eventName, String reservationId, int transactionId) {
Expand Down
Loading
Loading