add DomainSetup-HostingAssets for new BookingItem via created-event #111

Merged
hsh-michaelhoennig merged 31 commits from add-hsoting-asset-to-booking-item-resource-and-created-event into master 2024-10-08 11:48:35 +02:00
8 changed files with 140 additions and 83 deletions
Showing only changes of commit 0223cc1929 - Show all commits

View File

@ -0,0 +1,20 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import jakarta.validation.constraints.NotNull;
@Getter
public class BookingItemCreatedAppEvent extends ApplicationEvent {
private BookingItemCreatedEventEntity entity;
public BookingItemCreatedAppEvent(
@NotNull final HsBookingItemController source,
@NotNull final HsBookingItemRealEntity newBookingItem,
final String assetJson) {
super(source);
this.entity = new BookingItemCreatedEventEntity(newBookingItem, assetJson);
}
}

View File

@ -1,60 +0,0 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import jakarta.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Getter
public class BookingItemCreatedEvent extends ApplicationEvent {
private static final Map<UUID, BookingItemCreatedEvent> events = new HashMap<>(); // FIXME: use DB table
static BookingItemCreatedEvent of(final HsBookingItemRealEntity bookingItem) {
return events.get(bookingItem.getUuid());
}
@Getter
public static class Status {
private final String message;
private Status(final String message) {
this.message = message;
}
public static Status finished() {
return new Status(null);
}
public static Status failed(final String errorMessage) {
return new Status(errorMessage);
}
boolean isFinished() {
return message == null;
}
}
private final @NotNull UUID bookingItemUuid;
private final @NotNull String assetJson;
private Status status;
public BookingItemCreatedEvent(
@NotNull final HsBookingItemController source,
@NotNull final HsBookingItem newBookingItem,
final String assetJson) {
super(source);
this.bookingItemUuid = newBookingItem.getUuid();
this.assetJson = assetJson;
}
public void setStatus(final Status status) {
this.status = status;
events.put(bookingItemUuid, this);
}
}

View File

@ -0,0 +1,60 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.Table;
import jakarta.persistence.Version;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
@Entity
@Table(schema = "hs_booking", name = "item_created_event")
@SuperBuilder(toBuilder = true)
@Getter
@NoArgsConstructor
public class BookingItemCreatedEventEntity {
@Id
@Column(name="bookingitemuuid")
private UUID id;
@MapsId
@ManyToOne(optional = false)
@JoinColumn(name = "bookingitemuuid", nullable = false)
private HsBookingItemRealEntity bookingItem;
@Version
private int version;
@Column(name = "assetjson")
private String assetJson;
@Setter
@Column(name = "statusmessage")
private String statusMessage;
@Setter
@Column
private boolean completed;
public void setBookingItem(HsBookingItemRealEntity bookingItem) {
this.bookingItem = bookingItem;
}
public BookingItemCreatedEventEntity(
@NotNull final HsBookingItemRealEntity newBookingItem,
final String assetJson) {
this.bookingItem = newBookingItem;
this.assetJson = assetJson;
}
}

View File

@ -0,0 +1,12 @@
package net.hostsharing.hsadminng.hs.booking.item;
import org.springframework.data.repository.Repository;
import java.util.UUID;
public interface BookingItemCreatedEventRepository extends Repository<BookingItemCreatedEventEntity, UUID> {
BookingItemCreatedEventEntity save(HsBookingItemRealEntity current);
BookingItemCreatedEventEntity findByBookingItem(HsBookingItemRealEntity newBookingItem);
}

View File

@ -83,8 +83,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
.revampProperties(); .revampProperties();
try { try {
applicationEventPublisher.publishEvent(new BookingItemCreatedEvent( final var bookingItemRealEntity = em.getReference(HsBookingItemRealEntity.class, saveProcessor.getEntity().getUuid());
this, saveProcessor.getEntity(), jsonMapper.writeValueAsString(body.getAsset()))); applicationEventPublisher.publishEvent(new BookingItemCreatedAppEvent(
this, bookingItemRealEntity, jsonMapper.writeValueAsString(body.getAsset())));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -5,8 +5,7 @@ import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource; import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource;
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEvent; import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEvent.Status;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor; import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
@ -28,7 +27,7 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMA
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SMTP_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SMTP_SETUP;
@Component @Component
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedEvent> { public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
@Autowired @Autowired
private EntityManagerWrapper emw; private EntityManagerWrapper emw;
@ -41,19 +40,19 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
@Override @Override
@SneakyThrows @SneakyThrows
public void onApplicationEvent(final BookingItemCreatedEvent event) { public void onApplicationEvent(final BookingItemCreatedAppEvent event) {
final var newBookingItemRealEntity = final var newBookingItemRealEntity = event.getEntity().getBookingItem();
emw.getReference(HsBookingItemRealEntity.class, event.getBookingItemUuid()); final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
final var asset = jsonMapper.readValue(event.getAssetJson(), HsHostingAssetAutoInsertResource.class);
final var factory = switch (newBookingItemRealEntity.getType()) { final var factory = switch (newBookingItemRealEntity.getType()) {
case PRIVATE_CLOUD -> null; case PRIVATE_CLOUD -> null;
case CLOUD_SERVER -> null; case CLOUD_SERVER -> null;
case MANAGED_SERVER -> null; case MANAGED_SERVER -> null;
case MANAGED_WEBSPACE -> null; case MANAGED_WEBSPACE -> null; // TODO.impl: implement ManagedWebspace HostingAsset creation, where possible
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(newBookingItemRealEntity, asset); case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(newBookingItemRealEntity, asset);
}; };
if (factory != null) { if (factory != null) {
event.setStatus(factory.performSaveProcess()); event.getEntity().setStatusMessage(factory.performSaveProcess());
emw.persist(event.getEntity()); // TODO.impl: once we implement retry, we might need merge
} }
} }
@ -71,23 +70,23 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
return null; return null;
} }
Status performSaveProcess() { String performSaveProcess() {
final var newHostingAsset = create(); final var newHostingAsset = create();
try { try {
return persist(newHostingAsset); persist(newHostingAsset);
return null;
} catch (final Exception e) { } catch (final Exception e) {
return Status.failed(e.getMessage()); return e.getMessage();
} }
} }
protected Status persist(final HsHostingAsset newHostingAsset) { protected void persist(final HsHostingAsset newHostingAsset) {
new HostingAssetEntitySaveProcessor(emw, newHostingAsset) new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
.preprocessEntity() .preprocessEntity()
.validateEntity() .validateEntity()
.prepareForSave() .prepareForSave()
.save() .save()
.validateContext(); .validateContext();
return Status.finished();
} }
} }
@ -166,10 +165,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
} }
@Override @Override
protected Status persist(final HsHostingAsset newHostingAsset) { protected void persist(final HsHostingAsset newHostingAsset) {
final var status = super.persist(newHostingAsset); super.persist(newHostingAsset);
newHostingAsset.getSubHostingAssets().forEach(super::persist); newHostingAsset.getSubHostingAssets().forEach(super::persist);
return status;
} }
} }
} }

View File

@ -31,6 +31,21 @@ create table if not exists hs_booking.item
--// --//
-- ============================================================================
--changeset michael.hoennig:hs-booking-item-EVENT-TABLE endDelimiter:--//
-- ----------------------------------------------------------------------------
create table if not exists hs_booking.item_created_event
(
bookingItemUuid uuid unique references hs_booking.item (uuid),
version int not null default 0,
assetJson text,
statusMessage text,
completed boolean not null default false
);
--//
-- ============================================================================ -- ============================================================================
--changeset michael.hoennig:hs-booking-item-MAIN-TABLE-JOURNAL endDelimiter:--// --changeset michael.hoennig:hs-booking-item-MAIN-TABLE-JOURNAL endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -13,6 +13,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns;
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt; import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -61,6 +62,9 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
@Autowired @Autowired
HsHostingAssetRealRepository realHostingAssetRepo; HsHostingAssetRealRepository realHostingAssetRepo;
@Autowired
BookingItemCreatedEventRepository bookingItemCreationEventRepo;
@Autowired @Autowired
JpaAttempt jpaAttempt; JpaAttempt jpaAttempt;
@ -261,8 +265,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
assertThat(realHostingAssetRepo.findByIdentifier("example.org|SMTP")).isNotEmpty() assertThat(realHostingAssetRepo.findByIdentifier("example.org|SMTP")).isNotEmpty()
.map(HsHostingAsset::getParentAsset) .map(HsHostingAsset::getParentAsset)
.isEqualTo(domainSetupHostingAsset); .isEqualTo(domainSetupHostingAsset);
final var status = BookingItemCreatedEvent.of(newBookingItem); final var event = bookingItemCreationEventRepo.findByBookingItem(newBookingItem);
assertThat(status.getStatus().isFinished()); assertThat(event.isCompleted());
} }
@ -328,8 +332,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
assertThat(newBookingItem) assertThat(newBookingItem)
.extracting(bi -> bi.getDirectValue("domainName", String.class)) .extracting(bi -> bi.getDirectValue("domainName", String.class))
.isEqualTo("example.org"); .isEqualTo("example.org");
final var status = BookingItemCreatedEvent.of(newBookingItem); final var event = bookingItemCreationEventRepo.findByBookingItem(newBookingItem);
assertThat(status.getStatus().getMessage()) assertThat(event.getStatusMessage())
.isEqualTo("[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=just-a-fake-verification-code' found for domain name 'example.org' (nor in its super-domain)]"); .isEqualTo("[[DNS] no TXT record 'Hostsharing-domain-setup-verification-code=just-a-fake-verification-code' found for domain name 'example.org' (nor in its super-domain)]");
// but the related HostingAsset did not get created // but the related HostingAsset did not get created
@ -565,6 +569,13 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
}).assertSuccessful().returnedValue(); }).assertSuccessful().returnedValue();
} }
@AfterEach
void cleanupEventEntities() {
jpaAttempt.transacted(() -> {
em.createQuery("delete from BookingItemCreatedEventEntity").executeUpdate();
}).assertSuccessful();
}
private Map.Entry<String, Object> resource(final String key, final Object value) { private Map.Entry<String, Object> resource(final String key, final Object value) {
return entry(key, value); return entry(key, value);
} }