Ejemplo n.º 1
0
@RestController
@RequestMapping("/admin/api")
@Log4j2
public class EventApiController {

  private static final String OK = "OK";
  private final EventManager eventManager;
  private final EventStatisticsManager eventStatisticsManager;
  private final I18nManager i18nManager;
  private final TicketReservationManager ticketReservationManager;
  private final TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository;
  private final TicketFieldRepository ticketFieldRepository;
  private final DescriptionsLoader descriptionsLoader;
  private final TicketHelper ticketHelper;
  private final DynamicFieldTemplateRepository dynamicFieldTemplateRepository;
  private final UserManager userManager;
  private final SponsorScanRepository sponsorScanRepository;
  private final PaymentManager paymentManager;

  @Autowired
  public EventApiController(
      EventManager eventManager,
      EventStatisticsManager eventStatisticsManager,
      I18nManager i18nManager,
      TicketReservationManager ticketReservationManager,
      TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository,
      TicketFieldRepository ticketFieldRepository,
      DescriptionsLoader descriptionsLoader,
      TicketHelper ticketHelper,
      DynamicFieldTemplateRepository dynamicFieldTemplateRepository,
      UserManager userManager,
      SponsorScanRepository sponsorScanRepository,
      PaymentManager paymentManager) {
    this.eventManager = eventManager;
    this.eventStatisticsManager = eventStatisticsManager;
    this.i18nManager = i18nManager;
    this.ticketReservationManager = ticketReservationManager;
    this.ticketCategoryDescriptionRepository = ticketCategoryDescriptionRepository;
    this.ticketFieldRepository = ticketFieldRepository;
    this.descriptionsLoader = descriptionsLoader;
    this.ticketHelper = ticketHelper;
    this.dynamicFieldTemplateRepository = dynamicFieldTemplateRepository;
    this.userManager = userManager;
    this.sponsorScanRepository = sponsorScanRepository;
    this.paymentManager = paymentManager;
  }

  @ExceptionHandler(DataAccessException.class)
  public String exception(DataAccessException e) {
    log.warn("unhandled exception", e);
    return "unexpected error. More info in the application log";
  }

  @ExceptionHandler(Exception.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public String unhandledException(Exception e) {
    if (!IllegalArgumentException.class.isInstance(e)) {
      log.warn("unhandled exception", e);
    }
    return e.getMessage();
  }

  @RequestMapping(value = "/paymentProxies/{organizationId}", method = GET)
  @ResponseStatus(HttpStatus.OK)
  public List<PaymentManager.PaymentMethod> getPaymentProxies(
      @PathVariable("organizationId") int organizationId, Principal principal) {
    return userManager
        .findUserOrganizations(principal.getName())
        .stream()
        .filter(o -> o.getId() == organizationId)
        .findFirst()
        .map(o -> paymentManager.getPaymentMethods(o.getId()))
        .orElse(Collections.emptyList());
  }

  @RequestMapping(value = "/events", method = GET, headers = "Authorization")
  public List<EventListItem> getAllEventsForExternal(
      Principal principal, HttpServletRequest request) {
    List<Integer> userOrganizations =
        userManager
            .findUserOrganizations(principal.getName())
            .stream()
            .map(Organization::getId)
            .collect(Collectors.toList());
    return eventManager
        .getActiveEvents()
        .stream()
        .filter(e -> userOrganizations.contains(e.getOrganizationId()))
        .sorted(
            (e1, e2) ->
                e1.getBegin()
                    .withZoneSameInstant(ZoneId.systemDefault())
                    .compareTo(e2.getBegin().withZoneSameInstant(ZoneId.systemDefault())))
        .map(
            s ->
                new EventListItem(
                    s, request.getContextPath(), descriptionsLoader.eventDescriptions()))
        .collect(Collectors.toList());
  }

  @RequestMapping(value = "/events", method = GET)
  public List<EventWithStatistics> getAllEvents(Principal principal) {
    return eventStatisticsManager
        .getAllEventsWithStatistics(principal.getName())
        .stream()
        .sorted()
        .collect(Collectors.toList());
  }

  @RequestMapping(value = "/events/{name}", method = GET)
  public Map<String, Object> getSingleEvent(
      @PathVariable("name") String eventName, Principal principal) {
    Map<String, Object> out = new HashMap<>();
    final String username = principal.getName();
    final EventWithStatistics event =
        eventStatisticsManager.getSingleEventWithStatistics(eventName, username);
    out.put("event", event);
    out.put("organization", eventManager.loadOrganizer(event.getEvent(), username));
    return out;
  }

  @RequestMapping(value = "/events/{eventId}", method = DELETE)
  public void deleteEvent(@PathVariable("eventId") int eventId, Principal principal) {
    eventManager.deleteEvent(eventId, principal.getName());
  }

  @RequestMapping(value = "/events/id/{eventId}", method = GET)
  public Event getSingleEventById(@PathVariable("eventId") int eventId, Principal principal) {
    return eventManager.getSingleEventById(eventId, principal.getName());
  }

  @RequestMapping(value = "/events/check", method = POST)
  public ValidationResult validateEvent(
      @RequestBody EventModification eventModification, Errors errors) {
    ValidationResult base =
        validateEventHeader(Optional.<Event>empty(), eventModification, errors)
            .or(validateEventPrices(Optional.<Event>empty(), eventModification, errors))
            .or(
                eventModification
                    .getAdditionalServices()
                    .stream()
                    .map(as -> validateAdditionalService(as, eventModification, errors))
                    .reduce(ValidationResult::or)
                    .orElse(ValidationResult.success()));
    AtomicInteger counter = new AtomicInteger();
    return base.or(
            eventModification
                .getTicketCategories()
                .stream()
                .map(
                    c ->
                        validateCategory(
                            c, errors, "ticketCategories[" + counter.getAndIncrement() + "]."))
                .reduce(ValidationResult::or)
                .orElse(ValidationResult.success()))
        .or(validateAdditionalTicketFields(eventModification.getTicketFields(), errors));
  }

  private ValidationResult validateAdditionalTicketFields(
      List<EventModification.AdditionalField> ticketFields, Errors errors) {
    // meh
    AtomicInteger cnt = new AtomicInteger();
    return Optional.ofNullable(ticketFields)
        .orElseGet(Collections::emptyList)
        .stream()
        .map(
            field -> {
              String prefix = "ticketFields[" + cnt.getAndIncrement() + "]";
              if (StringUtils.isBlank(field.getName())) {
                errors.rejectValue(prefix + ".name", "error.required");
              }
              // TODO: check label value is present for all the locales
              // TODO: for select check option value+label

              return Validator.evaluateValidationResult(errors);
            })
        .reduce(ValidationResult::or)
        .orElseGet(ValidationResult::success);
  }

  @RequestMapping(value = "/events/new", method = POST)
  public String insertEvent(@RequestBody EventModification eventModification) {
    eventManager.createEvent(eventModification);
    return OK;
  }

  @RequestMapping(value = "/events/{id}/status", method = PUT)
  public String activateEvent(
      @PathVariable("id") int id, @RequestParam("active") boolean active, Principal principal) {
    eventManager.toggleActiveFlag(id, principal.getName(), active);
    return OK;
  }

  @RequestMapping(value = "/events/{id}/header/update", method = POST)
  public ValidationResult updateHeader(
      @PathVariable("id") int id,
      @RequestBody EventModification eventModification,
      Errors errors,
      Principal principal) {
    Event event = eventManager.getSingleEventById(id, principal.getName());
    return validateEventHeader(Optional.of(event), eventModification, errors)
        .ifSuccess(
            () -> eventManager.updateEventHeader(event, eventModification, principal.getName()));
  }

  @RequestMapping(value = "/events/{id}/prices/update", method = POST)
  public ValidationResult updatePrices(
      @PathVariable("id") int id,
      @RequestBody EventModification eventModification,
      Errors errors,
      Principal principal) {
    Event event = eventManager.getSingleEventById(id, principal.getName());
    return validateEventPrices(Optional.of(event), eventModification, errors)
        .ifSuccess(
            () -> eventManager.updateEventPrices(event, eventModification, principal.getName()));
  }

  @RequestMapping(value = "/events/{eventId}/categories/{categoryId}/update", method = POST)
  public ValidationResult updateExistingCategory(
      @PathVariable("eventId") int eventId,
      @PathVariable("categoryId") int categoryId,
      @RequestBody TicketCategoryModification category,
      Errors errors,
      Principal principal) {
    return validateCategory(category, errors)
        .ifSuccess(
            () -> eventManager.updateCategory(categoryId, eventId, category, principal.getName()));
  }

  @RequestMapping(value = "/events/{eventId}/categories/new", method = POST)
  public ValidationResult createCategory(
      @PathVariable("eventId") int eventId,
      @RequestBody TicketCategoryModification category,
      Errors errors,
      Principal principal) {
    return validateCategory(category, errors)
        .ifSuccess(() -> eventManager.insertCategory(eventId, category, principal.getName()));
  }

  @RequestMapping(value = "/events/reallocate", method = PUT)
  public String reallocateTickets(@RequestBody TicketAllocationModification form) {
    eventManager.reallocateTickets(
        form.getSrcCategoryId(), form.getTargetCategoryId(), form.getEventId());
    return OK;
  }

  @RequestMapping(value = "/events/{eventName}/category/{categoryId}/unbind-tickets", method = PUT)
  public String unbindTickets(
      @PathVariable("eventName") String eventName,
      @PathVariable("categoryId") int categoryId,
      Principal principal) {
    eventManager.unbindTickets(eventName, categoryId, principal.getName());
    return OK;
  }

  private static final List<String> FIXED_FIELDS =
      Arrays.asList(
          "ID",
          "creation",
          "category",
          "event",
          "status",
          "originalPrice",
          "paidPrice",
          "discount",
          "vat",
          "reservationID",
          "Full Name",
          "First Name",
          "Last Name",
          "E-Mail",
          "locked",
          "Language");
  private static final int[] BOM_MARKERS = new int[] {0xEF, 0xBB, 0xBF};

  @RequestMapping("/events/{eventName}/export.csv")
  public void downloadAllTicketsCSV(
      @PathVariable("eventName") String eventName,
      HttpServletRequest request,
      HttpServletResponse response,
      Principal principal)
      throws IOException {
    List<String> fields =
        Arrays.asList(
            Optional.ofNullable(request.getParameterValues("fields")).orElse(new String[] {}));
    Event event = loadEvent(eventName, principal);
    Map<Integer, TicketCategory> categoriesMap =
        eventManager
            .loadTicketCategories(event)
            .stream()
            .collect(Collectors.toMap(TicketCategory::getId, Function.identity()));
    ZoneId eventZoneId = event.getZoneId();

    Predicate<String> contains = FIXED_FIELDS::contains;

    response.setContentType("text/csv;charset=UTF-8");
    response.setHeader("Content-Disposition", "attachment; filename=" + eventName + "-export.csv");

    try (ServletOutputStream out = response.getOutputStream();
        CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {

      for (int marker :
          BOM_MARKERS) { // UGLY-MODE_ON: specify that the file is written in UTF-8 with BOM, thanks
                         // to alexr http://stackoverflow.com/a/4192897
        out.write(marker);
      }

      writer.writeNext(fields.toArray(new String[fields.size()]));

      eventManager
          .findAllConfirmedTickets(eventName, principal.getName())
          .stream()
          .map(
              t -> {
                List<String> line = new ArrayList<>();
                if (fields.contains("ID")) {
                  line.add(t.getUuid());
                }
                if (fields.contains("creation")) {
                  line.add(t.getCreation().withZoneSameInstant(eventZoneId).toString());
                }
                if (fields.contains("category")) {
                  line.add(categoriesMap.get(t.getCategoryId()).getName());
                }
                if (fields.contains("event")) {
                  line.add(eventName);
                }
                if (fields.contains("status")) {
                  line.add(t.getStatus().toString());
                }
                if (fields.contains("originalPrice")) {
                  line.add(MonetaryUtil.centsToUnit(t.getSrcPriceCts()).toString());
                }
                if (fields.contains("paidPrice")) {
                  line.add(MonetaryUtil.centsToUnit(t.getFinalPriceCts()).toString());
                }
                if (fields.contains("discount")) {
                  line.add(MonetaryUtil.centsToUnit(t.getDiscountCts()).toString());
                }
                if (fields.contains("vat")) {
                  line.add(MonetaryUtil.centsToUnit(t.getVatCts()).toString());
                }
                if (fields.contains("reservationID")) {
                  line.add(t.getTicketsReservationId());
                }
                if (fields.contains("Full Name")) {
                  line.add(t.getFullName());
                }
                if (fields.contains("First Name")) {
                  line.add(t.getFirstName());
                }
                if (fields.contains("Last Name")) {
                  line.add(t.getLastName());
                }
                if (fields.contains("E-Mail")) {
                  line.add(t.getEmail());
                }
                if (fields.contains("locked")) {
                  line.add(String.valueOf(t.getLockedAssignment()));
                }
                if (fields.contains("Language")) {
                  line.add(String.valueOf(t.getUserLanguage()));
                }

                // obviously not optimized
                Map<String, String> additionalValues =
                    ticketFieldRepository.findAllValuesForTicketId(t.getId());

                fields
                    .stream()
                    .filter(contains.negate())
                    .forEachOrdered(
                        field -> {
                          line.add(additionalValues.getOrDefault(field, "").replaceAll("\"", ""));
                        });

                return line.toArray(new String[line.size()]);
              })
          .forEachOrdered(writer::writeNext);
      writer.flush();
      out.flush();
    }
  }

  @RequestMapping("/events/{eventName}/sponsor-scan/export.csv")
  public void downloadSponsorScanExport(
      @PathVariable("eventName") String eventName,
      HttpServletResponse response,
      Principal principal)
      throws IOException {
    Event event = loadEvent(eventName, principal);
    List<TicketFieldConfiguration> fields =
        ticketFieldRepository.findAdditionalFieldsForEvent(event.getId());

    response.setContentType("text/csv;charset=UTF-8");
    response.setHeader(
        "Content-Disposition", "attachment; filename=" + eventName + "-sponsor-scan.csv");

    try (ServletOutputStream out = response.getOutputStream();
        CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
      for (int marker : BOM_MARKERS) {
        out.write(marker);
      }

      List<String> header = new ArrayList<>();
      header.add("Username");
      header.add("Timestamp");
      header.add("Full name");
      header.add("Email");
      header.addAll(
          fields.stream().map(TicketFieldConfiguration::getName).collect(Collectors.toList()));
      writer.writeNext(header.toArray(new String[header.size()]));
      userManager
          .findAllUsers(principal.getName())
          .stream()
          .map(u -> Pair.of(u, userManager.getUserRole(u)))
          .filter(p -> p.getRight() == Role.SPONSOR)
          .flatMap(
              p ->
                  sponsorScanRepository
                      .loadSponsorData(
                          event.getId(),
                          p.getKey().getId(),
                          SponsorScanRepository.DEFAULT_TIMESTAMP)
                      .stream()
                      .map(
                          v ->
                              Pair.of(
                                  v,
                                  ticketFieldRepository.findAllValuesForTicketId(
                                      v.getTicket().getId()))))
          .map(
              p -> {
                DetailedScanData data = p.getLeft();
                Map<String, String> descriptions = p.getRight();
                return Pair.of(
                    data,
                    fields
                        .stream()
                        .map(x -> descriptions.getOrDefault(x.getName(), ""))
                        .collect(Collectors.toList()));
              })
          .map(
              p -> {
                List<String> line = new ArrayList<>();
                Ticket ticket = p.getLeft().getTicket();
                SponsorScan sponsorScan = p.getLeft().getSponsorScan();
                line.add(userManager.findUser(sponsorScan.getUserId()).getUsername());
                line.add(sponsorScan.getTimestamp().toString());
                line.add(ticket.getFullName());
                line.add(ticket.getEmail());
                line.addAll(p.getRight());
                return line.toArray(new String[line.size()]);
              })
          .forEachOrdered(writer::writeNext);
      writer.flush();
      out.flush();
    }
  }

  @RequestMapping("/events/{eventName}/fields")
  public List<String> getAllFields(@PathVariable("eventName") String eventName) {
    List<String> fields = new ArrayList<>();
    fields.addAll(FIXED_FIELDS);
    fields.addAll(ticketFieldRepository.findFieldsForEvent(eventName));
    return fields;
  }

  @RequestMapping("/events/{eventName}/additional-field")
  public List<TicketFieldConfigurationAndAllDescriptions> getAllAdditionalField(
      @PathVariable("eventName") String eventName) {
    final Map<Integer, List<TicketFieldDescription>> descById =
        ticketFieldRepository
            .findDescriptions(eventName)
            .stream()
            .collect(Collectors.groupingBy(TicketFieldDescription::getTicketFieldConfigurationId));
    return ticketFieldRepository
        .findAdditionalFieldsForEvent(eventName)
        .stream()
        .map(
            field ->
                new TicketFieldConfigurationAndAllDescriptions(
                    field, descById.getOrDefault(field.getId(), Collections.emptyList())))
        .collect(Collectors.toList());
  }

  @RequestMapping(value = "/event/additional-field/templates", method = GET)
  public List<DynamicFieldTemplate> loadTemplates() {
    return dynamicFieldTemplateRepository.loadAll();
  }

  @RequestMapping(value = "/events/{eventName}/additional-field/descriptions", method = POST)
  public void saveAdditionalFieldDescriptions(
      @RequestBody Map<String, TicketFieldDescriptionModification> descriptions) {
    eventManager.updateTicketFieldDescriptions(descriptions);
  }

  @RequestMapping(value = "/events/{eventName}/additional-field/new", method = POST)
  public void addAdditionalField(
      @PathVariable("eventName") String eventName,
      @RequestBody EventModification.AdditionalField field,
      Principal principal) {
    Event event = eventManager.getSingleEvent(eventName, principal.getName());
    eventManager.addAdditionalField(event, field);
  }

  @RequestMapping(
      value = "/events/{eventName}/additional-field/swap-position/{id1}/{id2}",
      method = POST)
  public void swapAdditionalFieldPosition(
      @PathVariable("eventName") String eventName,
      @PathVariable("id1") int id1,
      @PathVariable("id2") int id2,
      Principal principal) {
    Event event = eventManager.getSingleEvent(eventName, principal.getName());
    eventManager.swapAdditionalFieldPosition(event.getId(), id1, id2);
  }

  @RequestMapping(value = "/events/{eventName}/additional-field/{id}", method = DELETE)
  public void deleteAdditionalField(
      @PathVariable("eventName") String eventName, @PathVariable("id") int id) {
    eventManager.deleteAdditionalField(id);
  }

  @RequestMapping(value = "/events/{eventName}/pending-payments")
  public List<SerializablePair<TicketReservation, OrderSummary>> getPendingPayments(
      @PathVariable("eventName") String eventName, Principal principal) {
    return ticketReservationManager
        .getPendingPayments(
            eventStatisticsManager.getSingleEventWithStatistics(eventName, principal.getName()))
        .stream()
        .map(SerializablePair::fromPair)
        .collect(Collectors.toList());
  }

  @RequestMapping(
      value = "/events/{eventName}/pending-payments/{reservationId}/confirm",
      method = POST)
  public String confirmPayment(
      @PathVariable("eventName") String eventName,
      @PathVariable("reservationId") String reservationId,
      Principal principal,
      Model model,
      HttpServletRequest request) {
    ticketReservationManager.confirmOfflinePayment(loadEvent(eventName, principal), reservationId);
    ticketReservationManager
        .findById(reservationId)
        .filter(TicketReservation::isDirectAssignmentRequested)
        .ifPresent(
            reservation ->
                ticketHelper.directTicketAssignment(
                    eventName,
                    reservationId,
                    reservation.getEmail(),
                    reservation.getFullName(),
                    reservation.getFirstName(),
                    reservation.getLastName(),
                    reservation.getUserLanguage(),
                    Optional.empty(),
                    request,
                    model));
    return OK;
  }

  @RequestMapping(value = "/events/{eventName}/pending-payments/{reservationId}", method = DELETE)
  public String deletePendingPayment(
      @PathVariable("eventName") String eventName,
      @PathVariable("reservationId") String reservationId,
      Principal principal) {
    ticketReservationManager.deleteOfflinePayment(
        loadEvent(eventName, principal), reservationId, false);
    return OK;
  }

  @RequestMapping(value = "/events/{eventName}/pending-payments/bulk-confirmation", method = POST)
  public List<Triple<Boolean, String, String>> bulkConfirmation(
      @PathVariable("eventName") String eventName,
      Principal principal,
      @RequestBody UploadBase64FileModification file)
      throws IOException {

    try (InputStreamReader isr = new InputStreamReader(file.getInputStream());
        CSVReader reader = new CSVReader(isr)) {
      Event event = loadEvent(eventName, principal);
      return reader
          .readAll()
          .stream()
          .map(
              line -> {
                String reservationID = null;
                try {
                  Validate.isTrue(line.length >= 2);
                  reservationID = line[0];
                  ticketReservationManager.validateAndConfirmOfflinePayment(
                      reservationID, event, new BigDecimal(line[1]));
                  return Triple.of(Boolean.TRUE, reservationID, "");
                } catch (Exception e) {
                  return Triple.of(
                      Boolean.FALSE, Optional.ofNullable(reservationID).orElse(""), e.getMessage());
                }
              })
          .collect(Collectors.toList());
    }
  }

  @RequestMapping(
      value = "/events/{eventName}/categories/{categoryId}/tickets/{ticketId}/toggle-locking",
      method = PUT)
  public boolean toggleTicketLocking(
      @PathVariable("eventName") String eventName,
      @PathVariable("categoryId") int categoryId,
      @PathVariable("ticketId") int ticketId,
      Principal principal) {
    return eventManager.toggleTicketLocking(eventName, categoryId, ticketId, principal.getName());
  }

  @RequestMapping(value = "/events/{eventName}/languages", method = GET)
  public List<ContentLanguage> getAvailableLocales(@PathVariable("eventName") String eventName) {
    return i18nManager.getEventLanguages(eventName);
  }

  @RequestMapping(value = "/events-all-languages", method = GET)
  public List<ContentLanguage> getAllLanguages() {
    return i18nManager.getAvailableLanguages();
  }

  @RequestMapping(value = "/events-supported-languages", method = GET)
  public List<ContentLanguage> getSupportedLanguages() {
    return i18nManager.getSupportedLanguages();
  }

  @RequestMapping(value = "/events/{eventName}/categories-containing-tickets", method = GET)
  public List<TicketCategoryModification> getCategoriesWithTickets(
      @PathVariable("eventName") String eventName, Principal principal) {
    Event event = loadEvent(eventName, principal);
    return eventStatisticsManager
        .loadTicketCategoriesWithStats(event)
        .stream()
        .filter(tc -> !tc.getTickets().isEmpty())
        .map(
            tc ->
                TicketCategoryModification.fromTicketCategory(
                    tc.getTicketCategory(),
                    ticketCategoryDescriptionRepository.findByTicketCategoryId(tc.getId()),
                    event.getZoneId()))
        .collect(Collectors.toList());
  }

  private Event loadEvent(String eventName, Principal principal) {
    Optional<Event> singleEvent =
        optionally(() -> eventManager.getSingleEvent(eventName, principal.getName()));
    Validate.isTrue(singleEvent.isPresent(), "event not found");
    return singleEvent.get();
  }
}
Ejemplo n.º 2
0
  @RequestMapping("/events/{eventName}/export.csv")
  public void downloadAllTicketsCSV(
      @PathVariable("eventName") String eventName,
      HttpServletRequest request,
      HttpServletResponse response,
      Principal principal)
      throws IOException {
    List<String> fields =
        Arrays.asList(
            Optional.ofNullable(request.getParameterValues("fields")).orElse(new String[] {}));
    Event event = loadEvent(eventName, principal);
    Map<Integer, TicketCategory> categoriesMap =
        eventManager
            .loadTicketCategories(event)
            .stream()
            .collect(Collectors.toMap(TicketCategory::getId, Function.identity()));
    ZoneId eventZoneId = event.getZoneId();

    Predicate<String> contains = FIXED_FIELDS::contains;

    response.setContentType("text/csv;charset=UTF-8");
    response.setHeader("Content-Disposition", "attachment; filename=" + eventName + "-export.csv");

    try (ServletOutputStream out = response.getOutputStream();
        CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {

      for (int marker :
          BOM_MARKERS) { // UGLY-MODE_ON: specify that the file is written in UTF-8 with BOM, thanks
                         // to alexr http://stackoverflow.com/a/4192897
        out.write(marker);
      }

      writer.writeNext(fields.toArray(new String[fields.size()]));

      eventManager
          .findAllConfirmedTickets(eventName, principal.getName())
          .stream()
          .map(
              t -> {
                List<String> line = new ArrayList<>();
                if (fields.contains("ID")) {
                  line.add(t.getUuid());
                }
                if (fields.contains("creation")) {
                  line.add(t.getCreation().withZoneSameInstant(eventZoneId).toString());
                }
                if (fields.contains("category")) {
                  line.add(categoriesMap.get(t.getCategoryId()).getName());
                }
                if (fields.contains("event")) {
                  line.add(eventName);
                }
                if (fields.contains("status")) {
                  line.add(t.getStatus().toString());
                }
                if (fields.contains("originalPrice")) {
                  line.add(MonetaryUtil.centsToUnit(t.getSrcPriceCts()).toString());
                }
                if (fields.contains("paidPrice")) {
                  line.add(MonetaryUtil.centsToUnit(t.getFinalPriceCts()).toString());
                }
                if (fields.contains("discount")) {
                  line.add(MonetaryUtil.centsToUnit(t.getDiscountCts()).toString());
                }
                if (fields.contains("vat")) {
                  line.add(MonetaryUtil.centsToUnit(t.getVatCts()).toString());
                }
                if (fields.contains("reservationID")) {
                  line.add(t.getTicketsReservationId());
                }
                if (fields.contains("Full Name")) {
                  line.add(t.getFullName());
                }
                if (fields.contains("First Name")) {
                  line.add(t.getFirstName());
                }
                if (fields.contains("Last Name")) {
                  line.add(t.getLastName());
                }
                if (fields.contains("E-Mail")) {
                  line.add(t.getEmail());
                }
                if (fields.contains("locked")) {
                  line.add(String.valueOf(t.getLockedAssignment()));
                }
                if (fields.contains("Language")) {
                  line.add(String.valueOf(t.getUserLanguage()));
                }

                // obviously not optimized
                Map<String, String> additionalValues =
                    ticketFieldRepository.findAllValuesForTicketId(t.getId());

                fields
                    .stream()
                    .filter(contains.negate())
                    .forEachOrdered(
                        field -> {
                          line.add(additionalValues.getOrDefault(field, "").replaceAll("\"", ""));
                        });

                return line.toArray(new String[line.size()]);
              })
          .forEachOrdered(writer::writeNext);
      writer.flush();
      out.flush();
    }
  }