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

Configuration API #1249

Merged
merged 2 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,149 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package alfio.controller.api.v1.admin;

import alfio.manager.EventManager;
import alfio.manager.system.ConfigurationManager;
import alfio.manager.user.UserManager;
import alfio.model.modification.ConfigurationModification;
import alfio.model.system.Configuration;
import alfio.model.system.ConfigurationKeys;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/v1/admin/configuration")
public class ConfigurationApiV1Controller {

private final ConfigurationManager configurationManager;
private final EventManager eventManager;
private final UserManager userManager;

public ConfigurationApiV1Controller(ConfigurationManager configurationManager,
EventManager eventManager,
UserManager userManager) {
this.configurationManager = configurationManager;
this.eventManager = eventManager;
this.userManager = userManager;
}

@PutMapping("/organization/{organizationId}")
public ResponseEntity<String> saveConfigurationForOrganization(@PathVariable("organizationId") int organizationId,
@RequestBody Map<String, String> configurationKeyValues,
Principal principal) {
var configurationKeys = configurationKeyValues.keySet().stream()
.map(ConfigurationKeys::safeValueOf)
.collect(Collectors.toSet());
var validationErrorOptional = validateInput(organizationId, principal, configurationKeyValues, configurationKeys);
if (validationErrorOptional.isPresent()) {
return validationErrorOptional.get();
}
var existingIds = configurationManager.loadOrganizationConfig(organizationId, principal.getName()).values().stream()
.flatMap(List::stream)
.filter(c -> configurationKeys.contains(c.getConfigurationKey()))
.collect(Collectors.toMap(Configuration::getKey, Configuration::getId));
var toSave = configurationKeyValues.entrySet().stream()
.map(ckv -> new ConfigurationModification(existingIds.get(ckv.getKey()), ckv.getKey(), ckv.getValue()))
.collect(Collectors.toList());
configurationManager.saveAllOrganizationConfiguration(organizationId, toSave, principal.getName());
return ResponseEntity.ok().body("OK");
}

@PutMapping("/organization/{organizationId}/event/{slug}")
public ResponseEntity<String> saveConfigurationForEvent(@PathVariable("organizationId") int organizationId,
@PathVariable("slug") String eventSlug,
@RequestBody Map<String, String> configurationKeyValues,
Principal principal) {
var configurationKeys = configurationKeyValues.keySet().stream()
.map(ConfigurationKeys::safeValueOf)
.collect(Collectors.toSet());

var validationErrorOptional = validateInput(organizationId, principal, configurationKeyValues, configurationKeys);

if (validationErrorOptional.isPresent()) {
return validationErrorOptional.get();
}

var eventAndOrgId = eventManager.getEventAndOrganizationId(eventSlug, principal.getName());
if (eventAndOrgId.getOrganizationId() != organizationId) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
int eventId = eventAndOrgId.getId();
var existingIds = configurationManager.loadEventConfig(eventId, principal.getName()).values().stream()
.flatMap(List::stream)
.filter(c -> configurationKeys.contains(c.getConfigurationKey()))
.collect(Collectors.toMap(Configuration::getKey, Configuration::getId));
var toSave = configurationKeyValues.entrySet().stream()
.map(ckv -> new ConfigurationModification(existingIds.get(ckv.getKey()), ckv.getKey(), ckv.getValue()))
.collect(Collectors.toList());
configurationManager.saveAllEventConfiguration(eventId, organizationId, toSave, principal.getName());
return ResponseEntity.ok().body("OK");
}

private boolean checkUserIsMemberOfOrganization(int organizationId, Principal principal) {
return userManager.findUserOrganizations(principal.getName()).stream().noneMatch(o -> o.getId() == organizationId);
}

static class ConfigurationKeyValue {

private final ConfigurationKeys key;
private final String value;

@JsonCreator
ConfigurationKeyValue(@JsonProperty("key") ConfigurationKeys key, @JsonProperty("value") String value) {
this.key = key;
this.value = value;
}

public ConfigurationKeys getKey() {
return key;
}

public String getValue() {
return value;
}
}

private Optional<ResponseEntity<String>> validateInput(int organizationId,
Principal principal,
Map<String, String> configurationKeyValues,
Set<ConfigurationKeys> configurationKeys) {
if (checkUserIsMemberOfOrganization(organizationId, principal)) {
return Optional.of(ResponseEntity.status(HttpStatus.FORBIDDEN).build());
}

if (configurationKeys.size() != configurationKeyValues.size()) {
return Optional.of(ResponseEntity.badRequest().body("Request contains duplicate keys"));
}

if (configurationKeys.contains(ConfigurationKeys.NOT_RECOGNIZED)) {
return Optional.of(ResponseEntity.badRequest().body("Request contains unrecognized keys"));
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package alfio.controller.api.v1;

import alfio.TestConfiguration;
import alfio.config.DataSourceConfiguration;
import alfio.config.Initializer;
import alfio.controller.api.ControllerConfiguration;
import alfio.controller.api.v1.admin.ConfigurationApiV1Controller;
import alfio.controller.api.v1.admin.EventApiV1Controller;
import alfio.manager.user.UserManager;
import alfio.model.modification.OrganizationModification;
import alfio.model.system.ConfigurationKeyValuePathLevel;
import alfio.model.user.Organization;
import alfio.model.user.Role;
import alfio.model.user.User;
import alfio.repository.EventRepository;
import alfio.repository.system.ConfigurationRepository;
import alfio.repository.user.OrganizationRepository;
import alfio.test.util.AlfioIntegrationTest;
import alfio.test.util.IntegrationTestUtil;
import alfio.util.BaseIntegrationTest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;

import java.security.Principal;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static alfio.controller.api.v1.EventApiV1IntegrationTest.creationRequest;
import static alfio.model.system.ConfigurationKeys.*;
import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;


@AlfioIntegrationTest
@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class, ControllerConfiguration.class})
@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS, Initializer.PROFILE_INTEGRATION_TEST})
class ConfigurationApiV1IntegrationTest extends BaseIntegrationTest {

public static final List<String> OPTIONS_TO_MODIFY = List.of(GENERATE_ONLY_INVOICE.name(), USE_INVOICE_NUMBER_AS_ID.name(), VAT_NUMBER_IS_REQUIRED.name());
@Autowired
private ConfigurationRepository configurationRepository;
@Autowired
private EventRepository eventRepository;
@Autowired
private UserManager userManager;
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private EventApiV1Controller eventApiController;
@Autowired
private ConfigurationApiV1Controller controller;

private Principal mockPrincipal;
private Organization organization;

@BeforeEach
public void ensureConfiguration() {
IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository);

String organizationName = UUID.randomUUID().toString();
String username = UUID.randomUUID().toString();

var organizationModification = new OrganizationModification(null, organizationName, "[email protected]", "org", null, null);
userManager.createOrganization(organizationModification, null);
organization = organizationRepository.findByName(organizationName).orElseThrow();
userManager.insertUser(organization.getId(), username, "test", "test", "[email protected]", Role.API_CONSUMER, User.Type.INTERNAL, null);

this.mockPrincipal = Mockito.mock(Principal.class);
Mockito.when(mockPrincipal.getName()).thenReturn(username);
}

@Test
void addEventConfiguration() {
String slug = "test";
eventApiController.create(creationRequest(slug), mockPrincipal);
int eventId = eventRepository.findOptionalEventAndOrganizationIdByShortName(slug).orElseThrow().getId();
var existing = configurationRepository.findByEventAndKeys(organization.getId(), eventId, OPTIONS_TO_MODIFY);
assertTrue(existing.isEmpty());
assertEquals(1, configurationRepository.insertEventLevel(organization.getId(), eventId, GENERATE_ONLY_INVOICE.name(), "true", ""));
var payload = Map.ofEntries(
entry(GENERATE_ONLY_INVOICE.name(), "false"),
entry(USE_INVOICE_NUMBER_AS_ID.name(), "true"),
entry(VAT_NUMBER_IS_REQUIRED.name(), "true")
);
var response = controller.saveConfigurationForEvent(organization.getId(), slug, payload, mockPrincipal);
assertTrue(response.getStatusCode().is2xxSuccessful());
var modified = configurationRepository.findByEventAndKeys(organization.getId(), eventId, OPTIONS_TO_MODIFY);
assertEquals(3, modified.size());
for (ConfigurationKeyValuePathLevel kv : modified) {
assertEquals(kv.getConfigurationKey() == GENERATE_ONLY_INVOICE ? "false" : "true", kv.getValue());
}
}

@Test
void addOrganizationConfiguration() {
var existing = configurationRepository.findByOrganizationAndKeys(organization.getId(), OPTIONS_TO_MODIFY);
assertTrue(existing.isEmpty());
assertEquals(1, configurationRepository.insertOrganizationLevel(organization.getId(), GENERATE_ONLY_INVOICE.name(), "true", ""));
var payload = Map.ofEntries(
entry(GENERATE_ONLY_INVOICE.name(), "false"),
entry(USE_INVOICE_NUMBER_AS_ID.name(), "true"),
entry(VAT_NUMBER_IS_REQUIRED.name(), "true")
);
var response = controller.saveConfigurationForOrganization(organization.getId(), payload, mockPrincipal);
assertTrue(response.getStatusCode().is2xxSuccessful());
var modified = configurationRepository.findByOrganizationAndKeys(organization.getId(), OPTIONS_TO_MODIFY);
assertEquals(3, modified.size());
for (ConfigurationKeyValuePathLevel kv : modified) {
assertEquals(kv.getConfigurationKey() == GENERATE_ONLY_INVOICE ? "false" : "true", kv.getValue());
}
}
}