HsOfficePartnerController.java
package net.hostsharing.hsadminng.hs.office.partner;
import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import net.hostsharing.hsadminng.config.MessageTranslator;
import net.hostsharing.hsadminng.rbac.context.Context;
import net.hostsharing.hsadminng.errors.ReferenceNotFoundException;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactFromResourceConverter;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartnersApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRelInsertResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController
@PreAuthorize("isAuthenticated()")
@SecurityRequirement(name = "bearerAuth")
public class HsOfficePartnerController implements HsOfficePartnersApi {
@Autowired
private Context context;
@Autowired
private StrictMapper mapper;
@Autowired
private MessageTranslator translator;
@Autowired
private HsOfficeContactFromResourceConverter<HsOfficeContactRealEntity> contactFromResourceConverter;
@Autowired
private HsOfficePartnerRbacRepository rbacPartnerRepo;
@Autowired
private HsOfficeRelationRbacRepository rbacRelationRepo;
@Autowired
private HsOfficePersonRbacRepository rbacPersonRepo;
@Autowired
private HsOfficeContactRbacRepository rbacContactRepo;
@PersistenceContext
private EntityManager em;
@PostConstruct
public void init() {
mapper.addConverter(contactFromResourceConverter, HsOfficeContactInsertResource.class, HsOfficeContactRealEntity.class);
}
@Override
@Transactional(readOnly = true)
@Timed("app.office.partners.api.getListOfPartners")
public ResponseEntity<List<HsOfficePartnerResource>> getListOfPartners(
final String assumedRoles,
final String name) {
context.assumeRoles(assumedRoles);
final var entities = rbacPartnerRepo.findPartnerByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);
}
@Override
@Transactional
@Timed("app.office.partners.api.postNewPartner")
public ResponseEntity<HsOfficePartnerResource> postNewPartner(
final String assumedRoles,
final HsOfficePartnerInsertResource body) {
context.assumeRoles(assumedRoles);
final var entityToSave = createPartnerEntity(body);
final var saved = rbacPartnerRepo.save(entityToSave);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/office/partners/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped);
}
@Override
@Transactional(readOnly = true)
@Timed("app.office.partners.api.getSinglePartnerByUuid")
public ResponseEntity<HsOfficePartnerResource> getSinglePartnerByUuid(
final String assumedRoles,
final UUID partnerUuid) {
context.assumeRoles(assumedRoles);
final var result = rbacPartnerRepo.findByUuid(partnerUuid);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
@Override
@Transactional(readOnly = true)
@Timed("app.office.partners.api.getSinglePartnerByPartnerNumber")
public ResponseEntity<HsOfficePartnerResource> getSinglePartnerByPartnerNumber(
final String assumedRoles,
final Integer partnerNumber) {
context.assumeRoles(assumedRoles);
final var result = rbacPartnerRepo.findPartnerByPartnerNumber(partnerNumber);
if (result.isEmpty()) {
return ResponseEntity.notFound().build();
}
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
@Override
@Transactional
@Timed("app.office.partners.api.deletePartnerByUuid")
public ResponseEntity<Void> deletePartnerByUuid(
final String assumedRoles,
final UUID partnerUuid) {
context.assumeRoles(assumedRoles);
final var partnerToDelete = rbacPartnerRepo.findByUuid(partnerUuid);
if (partnerToDelete.isEmpty()) {
return ResponseEntity.notFound().build();
}
if (rbacPartnerRepo.deleteByUuid(partnerUuid) != 1) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
return ResponseEntity.noContent().build();
}
@Override
@Transactional
@Timed("app.office.partners.api.patchPartner")
public ResponseEntity<HsOfficePartnerResource> patchPartner(
final String assumedRoles,
final UUID partnerUuid,
final HsOfficePartnerPatchResource body) {
context.assumeRoles(assumedRoles);
final var current = rbacPartnerRepo.findByUuid(partnerUuid).orElseThrow();
final var previousPartnerPerson = current.getPartnerRel().getHolder();
new HsOfficePartnerEntityPatcher(mapper, em, rbacPersonRepo, rbacContactRepo, current).apply(body);
final var saved = rbacPartnerRepo.save(current);
optionallyCreateExPartnerRelation(saved, previousPartnerPerson);
optionallyUpdateRelatedRelations(saved, previousPartnerPerson);
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
private void optionallyCreateExPartnerRelation(final HsOfficePartnerRbacEntity saved, final HsOfficePersonRealEntity previousPartnerPerson) {
final var partnerPersonHasChanged = !saved.getPartnerRel().getHolder().getUuid().equals(previousPartnerPerson.getUuid());
if (partnerPersonHasChanged) {
rbacRelationRepo.save(HsOfficeRelationRbacEntity.builder()
.type(EX_PARTNER)
.mark(saved.getPartnerRel().getMark())
.anchor(saved.getPartnerRel().getHolder())
.holder(previousPartnerPerson)
.contact(saved.getPartnerRel().getContact())
.build());
}
}
private void optionallyUpdateRelatedRelations(final HsOfficePartnerRbacEntity saved, final HsOfficePersonRealEntity previousPartnerPerson) {
final var partnerPersonHasChanged = !saved.getPartnerRel().getHolder().getUuid().equals(previousPartnerPerson.getUuid());
if (partnerPersonHasChanged) {
// self-debitors of the old partner-person become self-debitors of the new partner person
em.createNativeQuery("""
UPDATE hs_office.relation_rv
SET holderUuid = :newPartnerPersonUuid
WHERE type = 'DEBITOR' AND
holderUuid = :oldPartnerPersonUuid AND anchorUuid = :oldPartnerPersonUuid
""")
.setParameter("oldPartnerPersonUuid", previousPartnerPerson.getUuid())
.setParameter("newPartnerPersonUuid", saved.getPartnerRel().getHolder().getUuid())
.executeUpdate();
// re-anchor all relations from the old partner person to the new partner persion
em.createNativeQuery("""
UPDATE hs_office.relation_rv
SET anchorUuid = :newPartnerPersonUuid
WHERE anchorUuid = :oldPartnerPersonUuid
""")
.setParameter("oldPartnerPersonUuid", previousPartnerPerson.getUuid())
.setParameter("newPartnerPersonUuid", saved.getPartnerRel().getHolder().getUuid())
.executeUpdate();
}
}
private HsOfficePartnerRbacEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
final var entityToSave = new HsOfficePartnerRbacEntity();
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
return entityToSave;
}
private HsOfficeRelationRealEntity persistPartnerRel(final HsOfficePartnerRelInsertResource resource) {
final var entity = new HsOfficeRelationRealEntity();
entity.setType(HsOfficeRelationType.PARTNER);
entity.setAnchor(ref(HsOfficePersonRealEntity.class, resource.getAnchorUuid()));
entity.setHolder(ref(HsOfficePersonRealEntity.class, resource.getHolderUuid()));
entity.setContact(ref(HsOfficeContactRealEntity.class, resource.getContactUuid()));
em.persist(entity);
return entity;
}
private <E extends BaseEntity> E ref(final Class<E> entityClass, final UUID uuid) {
try {
return em.getReference(entityClass, uuid);
} catch (final Throwable exc) {
throw new ReferenceNotFoundException(translator, entityClass, uuid, exc);
}
}
final BiConsumer<HsOfficePartnerRbacEntity, HsOfficePartnerResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setPartnerNumber(entity.getTaggedPartnerNumber());
};
}