1
0

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

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/111
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-10-08 11:48:31 +02:00
parent cc2b04472f
commit 60341bf644
77 changed files with 1558 additions and 273 deletions

View File

@@ -17,7 +17,7 @@ public class JsonObjectMapperConfiguration {
public Jackson2ObjectMapperBuilder customObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modules(new JsonNullableModule(), new JavaTimeModule())
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS)
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS, JsonParser.Feature.ALLOW_COMMENTS)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}

View File

@@ -27,7 +27,7 @@ public final class HashGenerator {
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"0123456789/.";
private static boolean couldBeHashEnabled; // TODO.impl: remove after legacy data is migrated
private static boolean couldBeHashEnabled; // TODO.legacy: remove after legacy data is migrated
public enum Algorithm {
LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"),

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 Object source,
@NotNull final HsBookingItemRealEntity newBookingItem,
final String assetJson) {
super(source);
this.entity = new BookingItemCreatedEventEntity(newBookingItem, assetJson);
}
}

View File

@@ -1,16 +0,0 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import jakarta.validation.constraints.NotNull;
@Getter
public class BookingItemCreatedEvent extends ApplicationEvent {
private final @NotNull HsBookingItem newBookingItem;
public BookingItemCreatedEvent(@NotNull HsBookingItemController source, @NotNull final HsBookingItem newBookingItem) {
super(source);
this.newBookingItem = newBookingItem;
}
}

View File

@@ -0,0 +1,55 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.persistence.BaseEntity;
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
@ToString
@NoArgsConstructor
public class BookingItemCreatedEventEntity implements BaseEntity {
@Id
@Column(name="bookingitemuuid")
private UUID uuid;
@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;
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

@@ -14,7 +14,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;

View File

@@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.hs.booking.item;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsApi;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
@@ -23,6 +25,7 @@ import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController
@@ -40,6 +43,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
@Autowired
private HsBookingItemRbacRepository bookingItemRepo;
@Autowired
private ObjectMapper jsonMapper;
@Autowired
private EntityManagerWrapper em;
@@ -76,8 +82,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
.validateContext()
.mapUsing(e -> mapper.map(e, HsBookingItemResource.class, ITEM_TO_RESOURCE_POSTMAPPER))
.revampProperties();
applicationEventPublisher.publishEvent(new BookingItemCreatedEvent(this, saveProcessor.getEntity()));
publishSavedEvent(saveProcessor, body);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
@@ -137,6 +142,16 @@ public class HsBookingItemController implements HsBookingItemsApi {
return ResponseEntity.ok(mapped);
}
private void publishSavedEvent(final BookingItemEntitySaveProcessor saveProcessor, final HsBookingItemInsertResource body) {
try {
final var bookingItemRealEntity = em.getReference(HsBookingItemRealEntity.class, saveProcessor.getEntity().getUuid());
applicationEventPublisher.publishEvent(new BookingItemCreatedAppEvent(
this, bookingItemRealEntity, jsonMapper.writeValueAsString(body.getHostingAsset())));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
final BiConsumer<HsBookingItem, HsBookingItemResource> ITEM_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setValidFrom(entity.getValidity().lower());
if (entity.getValidity().hasUpperBound()) {
@@ -148,6 +163,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setProject(em.find(HsBookingProjectRealEntity.class, resource.getProjectUuid()));
ofNullable(resource.getParentItemUuid())
.map(parentItemUuid -> em.find(HsBookingItemRealEntity.class, parentItemUuid))
.ifPresent(entity::setParentItem);
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
entity.putResources(KeyValueMap.from(resource.getResources()));
};

View File

@@ -48,7 +48,7 @@ public class BookingItemEntitySaveProcessor {
return this;
}
// TODO.impl: remove once the migration of legacy data is done
// TODO.legacy: remove once the migration of legacy data is done
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
public BookingItemEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) {
step("validateEntity", "prepareForSave");

View File

@@ -13,27 +13,21 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.validators.Dns.REGISTRA
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}";
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
public static final String TARGET_UNIX_USER_PROPERTY_NAME = "targetUnixUser";
public static final String WEBSPACE_NAME_REGEX = "[a-z][a-z0-9]{2}[0-9]{2}";
public static final String TARGET_UNIX_USER_NAME_REGEX = "^"+WEBSPACE_NAME_REGEX+"$|^"+WEBSPACE_NAME_REGEX+"-[a-z0-9\\._-]+$";
public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode";
HsDomainSetupBookingItemValidator() {
super(
// TODO.spec: feels wrong
stringProperty(DOMAIN_NAME_PROPERTY_NAME).writeOnce()
.maxLength(253)
.matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name")
.notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name")
.required(),
// TODO.legacy: remove the following property once we give up legacy compatibility
stringProperty(TARGET_UNIX_USER_PROPERTY_NAME).writeOnce()
.maxLength(253)
.matchesRegEx(TARGET_UNIX_USER_NAME_REGEX).describedAs("is not a valid unix-user name")
.writeOnce()
.required(),
stringProperty(VERIFICATION_CODE_PROPERTY_NAME)
.minLength(12)
.maxLength(64)

View File

@@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@@ -1,56 +0,0 @@
package net.hostsharing.hsadminng.hs.hosting.asset;
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEvent;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedEvent> {
@Autowired
private EntityManagerWrapper emw;
@Override
public void onApplicationEvent(final BookingItemCreatedEvent event) {
System.out.println("Received newly created booking item: " + event.getNewBookingItem());
final var newBookingItemRealEntity =
emw.getReference(HsBookingItemRealEntity.class, event.getNewBookingItem().getUuid());
final var newHostingAsset = switch (newBookingItemRealEntity.getType()) {
case PRIVATE_CLOUD -> null;
case CLOUD_SERVER -> null;
case MANAGED_SERVER -> null;
case MANAGED_WEBSPACE -> null;
case DOMAIN_SETUP -> createDomainSetupHostingAsset(newBookingItemRealEntity);
};
if (newHostingAsset != null) {
try {
new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
.preprocessEntity()
.validateEntity()
.prepareForSave()
.save()
.validateContext();
} catch (final Exception e) {
// TODO.impl: store status in a separate field, maybe enum+message
newBookingItemRealEntity.getResources().put("status", e.getMessage());
}
}
}
private HsHostingAsset createDomainSetupHostingAsset(final HsBookingItemRealEntity fromBookingItem) {
return HsHostingAssetRbacEntity.builder()
.bookingItem(fromBookingItem)
.type(HsHostingAssetType.DOMAIN_SETUP)
.identifier(fromBookingItem.getDirectValue("domainName", String.class))
.subHostingAssets(List.of(
// TARGET_UNIX_USER_PROPERTY_NAME
))
.build();
}
}

View File

@@ -14,7 +14,7 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
@@ -89,10 +89,9 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
@JoinColumn(name = "alarmcontactuuid")
private HsOfficeContactRealEntity alarmContact;
@Builder.Default
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
private List<HsHostingAssetRealEntity> subHostingAssets = new ArrayList<>();
private List<HsHostingAssetRealEntity> subHostingAssets;
@Column(name = "identifier")
private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc
@@ -125,6 +124,13 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config).assign(newConfig);
}
public List<HsHostingAssetRealEntity> getSubHostingAssets() {
if (subHostingAssets == null) {
subHostingAssets = new ArrayList<>();
}
return subHostingAssets;
}
@Override
public PatchableMapWrapper<Object> directProps() {
return getConfig();

View File

@@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -13,6 +14,18 @@ public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<H
List<HsHostingAssetRealEntity> findByIdentifier(String assetIdentifier);
default List<HsHostingAssetRealEntity> findByTypeAndIdentifier(@NotNull HsHostingAssetType type, @NotNull String identifier) {
return findByTypeAndIdentifierImpl(type.name(), identifier);
}
@Query("""
select ha
from HsHostingAssetRealEntity ha
where cast(ha.type as String) = :type
and ha.identifier = :identifier
""")
List<HsHostingAssetRealEntity> findByTypeAndIdentifierImpl(@NotNull String type, @NotNull String identifier);
@Query(value = """
select ha.uuid,
ha.alarmcontactuuid,

View File

@@ -0,0 +1,153 @@
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetSubInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetTypeResource;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.lambda.Reducer;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import jakarta.validation.ValidationException;
import java.net.IDN;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_HTTP_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_MBOX_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SMTP_SETUP;
public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
public DomainSetupHostingAssetFactory(
final EntityManagerWrapper emw,
final HsBookingItemRealEntity newBookingItemRealEntity,
final HsHostingAssetAutoInsertResource asset,
final StandardMapper standardMapper) {
super(emw, newBookingItemRealEntity, asset, standardMapper);
}
@Override
protected HsHostingAsset create() {
final var domainSetupAsset = createDomainSetupAsset(getDomainName());
final var subHostingAssets = domainSetupAsset.getSubHostingAssets();
// TODO.legacy: as long as we need to be compatible, we always do all technical domain-setups
final var domainHttpSetupAssetResource = findSubHostingAssetResource(HsHostingAssetTypeResource.DOMAIN_HTTP_SETUP);
final var assignedToUnixUserAssetEntity = domainHttpSetupAssetResource
.map(HsHostingAssetSubInsertResource::getAssignedToAssetUuid)
.map(uuid -> emw.find(HsHostingAssetRealEntity.class, uuid))
.orElseThrow(() -> new ValidationException("DOMAIN_HTTP_SETUP subAsset with assignedToAssetUuid required in compatibility mode"));
subHostingAssets.add(
createDomainSubSetupAssetEntity(
domainSetupAsset,
DOMAIN_HTTP_SETUP,
builder -> builder
.assignedToAsset(assignedToUnixUserAssetEntity)
.identifier(getDomainName() + "|HTTP")
.caption("HTTP-Setup für " + IDN.toUnicode(getDomainName())))
);
// Do not add to subHostingAssets in compatibility mode, in this case, DNS setup works via file system.
// The entity is created just for validation purposes.
createDomainSubSetupAssetEntity(
domainSetupAsset,
DOMAIN_DNS_SETUP,
builder -> builder
.assignedToAsset(assignedToUnixUserAssetEntity.getParentAsset())
.identifier(getDomainName() + "|DNS")
.caption("DNS-Setup für " + IDN.toUnicode(getDomainName())));
subHostingAssets.add(
createDomainSubSetupAssetEntity(
domainSetupAsset,
DOMAIN_MBOX_SETUP,
builder -> builder
.assignedToAsset(assignedToUnixUserAssetEntity.getParentAsset())
.identifier(getDomainName() + "|MBOX")
.caption("MBOX-Setup für " + IDN.toUnicode(getDomainName())))
);
subHostingAssets.add(
createDomainSubSetupAssetEntity(
domainSetupAsset,
DOMAIN_SMTP_SETUP,
builder -> builder
.assignedToAsset(assignedToUnixUserAssetEntity.getParentAsset())
.identifier(getDomainName() + "|SMTP")
.caption("SMTP-Setup für " + IDN.toUnicode(getDomainName())))
);
return domainSetupAsset;
}
private HsHostingAssetRealEntity createDomainSetupAsset(final String domainName) {
return HsHostingAssetRealEntity.builder()
.bookingItem(fromBookingItem)
.type(HsHostingAssetType.DOMAIN_SETUP)
.identifier(domainName)
.caption(asset.getCaption() != null ? asset.getCaption() : domainName)
.alarmContact(ref(HsOfficeContactRealEntity.class, asset.getAlarmContactUuid()))
// the sub-hosting-assets get added later
.build();
}
private HsHostingAssetRealEntity createDomainSubSetupAssetEntity(
final HsHostingAssetRealEntity domainSetupAsset,
final HsHostingAssetType subAssetType,
final Function<HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>, HsHostingAssetRealEntity.HsHostingAssetRealEntityBuilder<?, ?>> builderTransformer) {
final var resourceType = HsHostingAssetTypeResource.valueOf(subAssetType.name());
final var subAssetResourceOptional = findSubHostingAssetResource(resourceType);
subAssetResourceOptional.ifPresentOrElse(
subAssetResource -> verifyNotOverspecified(subAssetResource),
() -> { throw new ValidationException("sub-asset of type " + resourceType.name() + " required in legacy mode, but missing"); }
);
return builderTransformer.apply(
HsHostingAssetRealEntity.builder()
.type(subAssetType)
.parentAsset(domainSetupAsset))
.build();
}
private Optional<HsHostingAssetSubInsertResource> findSubHostingAssetResource(final HsHostingAssetTypeResource resourceType) {
return getSubHostingAssetResources().stream()
.filter(ha -> ha.getType() == resourceType)
.reduce(Reducer::toSingleElement);
}
// TODO.legacy: while we need to stay compatible, only default values can be used, thus only the type can be specified
private void verifyNotOverspecified(final HsHostingAssetSubInsertResource givenSubAssetResource) {
final var convert = new ToStringConverter().ignoring("assignedToAssetUuid");
final var expectedSubAssetResource = new HsHostingAssetSubInsertResource();
expectedSubAssetResource.setType(givenSubAssetResource.getType());
if ( !convert.from(givenSubAssetResource).equals(convert.from(expectedSubAssetResource)) ) {
throw new ValidationException("sub asset " + givenSubAssetResource.getType() + " is over-specified, in compatibility mode, only default values allowed");
}
}
private String getDomainName() {
return asset.getIdentifier();
}
private List<HsHostingAssetSubInsertResource> getSubHostingAssetResources() {
return asset.getSubHostingAssets();
}
@Override
protected void persist(final HsHostingAsset newHostingAsset) {
super.persist(newHostingAsset);
newHostingAsset.getSubHostingAssets().forEach(super::persist);
}
}

View File

@@ -0,0 +1,45 @@
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
import lombok.RequiredArgsConstructor;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import java.util.UUID;
@RequiredArgsConstructor
abstract class HostingAssetFactory {
final EntityManagerWrapper emw;
final HsBookingItemRealEntity fromBookingItem;
final HsHostingAssetAutoInsertResource asset;
final StandardMapper standardMapper;
protected abstract HsHostingAsset create();
public String performSaveProcess() {
try {
final var newHostingAsset = create();
persist(newHostingAsset);
return null;
} catch (final Exception e) {
return e.getMessage();
}
}
protected void persist(final HsHostingAsset newHostingAsset) {
new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
.preprocessEntity()
.validateEntity()
.prepareForSave()
.save()
.validateContext();
}
protected <T> T ref(final Class<T> entityClass, final UUID uuid) {
return uuid != null ? emw.getReference(entityClass, uuid) : null;
}
}

View File

@@ -0,0 +1,80 @@
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAssetAutoInsertResource;
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
@Autowired
private EntityManagerWrapper emw;
@Autowired
private ObjectMapper jsonMapper;
@Autowired
private StandardMapper standardMapper;
@Override
@SneakyThrows
public void onApplicationEvent(final BookingItemCreatedAppEvent bookingItemCreatedAppEvent) {
if (containsAssetJson(bookingItemCreatedAppEvent)) {
createRelatedHostingAsset(bookingItemCreatedAppEvent);
}
}
private static boolean containsAssetJson(final BookingItemCreatedAppEvent bookingItemCreatedAppEvent) {
return bookingItemCreatedAppEvent.getEntity().getAssetJson() != null;
}
private void createRelatedHostingAsset(final BookingItemCreatedAppEvent event) throws JsonProcessingException {
final var newBookingItemRealEntity = event.getEntity().getBookingItem();
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
final var factory = switch (newBookingItemRealEntity.getType()) {
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, standardMapper);
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
};
if (factory != null) {
final var statusMessage = factory.performSaveProcess();
// TODO.impl: once we implement retry, we need to amend this code (persist/merge/delete)
if (statusMessage != null) {
event.getEntity().setStatusMessage(statusMessage);
emw.persist(event.getEntity());
}
}
}
private HostingAssetFactory forNowNoAutomaticHostingAssetCreationPossible(
final EntityManagerWrapper emw,
final HsBookingItemRealEntity fromBookingItem,
final HsHostingAssetAutoInsertResource asset,
final StandardMapper standardMapper
) {
return new HostingAssetFactory(emw, fromBookingItem, asset, standardMapper) {
@Override
protected HsHostingAsset create() {
// TODO.impl: we should validate the asset JSON, but some violations are un-avoidable at that stage
return null;
}
@Override
public String performSaveProcess() {
return "waiting for manual setup of hosting asset for booking item of type " + fromBookingItem.getType();
}
};
}
}

View File

@@ -0,0 +1,51 @@
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
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.item.HsBookingItemRealEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import jakarta.validation.ValidationException;
import java.util.Optional;
public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
public ManagedWebspaceHostingAssetFactory(
final EntityManagerWrapper emw,
final HsBookingItemRealEntity newBookingItemRealEntity,
final HsHostingAssetAutoInsertResource asset,
final StandardMapper standardMapper) {
super(emw, newBookingItemRealEntity, asset, standardMapper);
}
@Override
protected HsHostingAsset create() {
if (asset.getType() != HsHostingAssetTypeResource.MANAGED_WEBSPACE) {
throw new ValidationException("requires MANAGED_WEBSPACE hosting asset, but got " +
Optional.of(asset)
.map(HsHostingAssetAutoInsertResource::getType)
.map(Enum::name)
.orElse(null));
}
final var managedWebspaceHostingAsset = standardMapper.map(asset, HsHostingAssetRealEntity.class);
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
emw.createQuery(
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",
HsHostingAssetRealEntity.class)
.setParameter("bookingItemUuid", fromBookingItem.getParentItem().getUuid())
.getResultStream().findFirst()
.ifPresent(managedWebspaceHostingAsset::setParentAsset);
return managedWebspaceHostingAsset;
}
@Override
protected void persist(final HsHostingAsset newManagedWebspaceHostingAsset) {
super.persist(newManagedWebspaceHostingAsset);
}
}

View File

@@ -0,0 +1,37 @@
package net.hostsharing.hsadminng.hs.hosting.asset.factories;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static java.util.stream.Collectors.joining;
public class ToStringConverter {
final public Set<String> ignoredFields = new HashSet<>();
public ToStringConverter ignoring(final String fieldName) {
ignoredFields.add(fieldName);
return this;
}
public String from(Object obj) {
StringBuilder result = new StringBuilder();
return "{ " +
Arrays.stream(obj.getClass().getDeclaredFields())
.filter(f -> !ignoredFields.contains(f.getName()))
.map(field -> {
try {
field.setAccessible(true);
return field.getName() + ": " + field.get(obj);
} catch (IllegalAccessException e) {
// ignore inaccessible fields
return null;
}
})
.filter(Objects::nonNull)
.collect(joining(", "))
+ " }";
}
}

View File

@@ -42,7 +42,7 @@ public class HostingAssetEntitySaveProcessor {
return this;
}
// TODO.impl: remove once the migration of legacy data is done
// TODO.legacy: remove once the migration of legacy data is done
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) {
step("validateEntity", "prepareForSave");

View File

@@ -15,7 +15,7 @@ import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanPro
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringProperty;
// TODO.impl: make package private once we've migrated the legacy data
// TODO.legacy: make package private once we've migrated the legacy data
public class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator {
// according to RFC 1035 (section 5) and RFC 1034
@@ -33,7 +33,7 @@ public class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityVal
RR_REGEX_NAME + RR_REGEX_IN + RR_REGEX_TTL + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT;
public static final String IDENTIFIER_SUFFIX = "|DNS";
private static List<String> zoneFileErrors = null; // TODO.impl: remove once legacy data is migrated
private static List<String> zoneFileErrors = null; // TODO.legacy: remove once legacy data is migrated
HsDomainDnsSetupHostingAssetValidator() {
super(

View File

@@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.bankaccount;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@@ -11,7 +11,7 @@ import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;

View File

@@ -8,7 +8,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@@ -8,7 +8,7 @@ import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;

View File

@@ -9,7 +9,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;

View File

@@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

View File

@@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;

View File

@@ -10,7 +10,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;

View File

@@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;

View File

@@ -5,7 +5,7 @@ import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@@ -7,7 +7,7 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@@ -31,7 +31,7 @@ public class PasswordProperty extends StringProperty<PasswordProperty> {
@Override
protected void validate(final List<String> result, final String propValue, final PropertiesProvider propProvider) {
// TODO.impl: remove after legacy data is migrated
// TODO.legacy: remove after legacy data is migrated
if (HashGenerator.using(hashedUsing).couldBeHash(propValue) && propValue.length() > this.maxLength()) {
// already hashed => do not validate
return;

View File

@@ -0,0 +1,8 @@
package net.hostsharing.hsadminng.lambda;
public class Reducer {
public static <T> T toSingleElement(T last, T next) {
throw new AssertionError("only a single entity expected");
}
}

View File

@@ -1,11 +1,11 @@
package net.hostsharing.hsadminng.mapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;
import jakarta.persistence.EntityManager;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PersistenceContext;
import jakarta.validation.ValidationException;
import java.lang.reflect.Field;
import java.util.List;
@@ -21,10 +21,10 @@ import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
*/
abstract class Mapper extends ModelMapper {
@PersistenceContext
EntityManager em;
EntityManagerWrapper em;
Mapper() {
Mapper(@Autowired final EntityManagerWrapper em) {
this.em = em;
getConfiguration().setAmbiguityIgnored(true);
}

View File

@@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.mapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
@@ -8,7 +10,8 @@ import org.springframework.stereotype.Component;
@Component
public class StandardMapper extends Mapper {
public StandardMapper() {
public StandardMapper(@Autowired final EntityManagerWrapper em) {
super(em);
getConfiguration().setAmbiguityIgnored(true);
}
}

View File

@@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.mapper;
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import static org.modelmapper.convention.MatchingStrategies.STRICT;
@@ -13,7 +15,8 @@ import static org.modelmapper.convention.MatchingStrategies.STRICT;
@Component
public class StrictMapper extends Mapper {
public StrictMapper() {
public StrictMapper(@Autowired final EntityManagerWrapper em) {
super(em);
getConfiguration().setMatchingStrategy(STRICT);
}
}

View File

@@ -1,11 +1,10 @@
package net.hostsharing.hsadminng.rbac.object;
package net.hostsharing.hsadminng.persistence;
import org.hibernate.Hibernate;
import java.util.UUID;
// TODO.impl: this class does not really belong into this package, but there is no right place yet
public interface BaseEntity<T extends BaseEntity<?>> {
UUID getUuid();

View File

@@ -1,7 +1,6 @@
package net.hostsharing.hsadminng.persistence;
import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

View File

@@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.generator;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import org.reflections.Reflections;
import org.reflections.scanners.TypeAnnotationsScanner;

View File

@@ -5,7 +5,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;

View File

@@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.rbac.test.pac.TestPackageEntity;

View File

@@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.rbac.test.cust.TestCustomerEntity;

View File

@@ -56,6 +56,10 @@ components:
type: string
format: uuid
nullable: false
parentItemUuid:
type: string
format: uuid
nullable: false
type:
$ref: '#/components/schemas/HsBookingItemType'
caption:
@@ -69,6 +73,8 @@ components:
nullable: true
resources:
$ref: '#/components/schemas/BookingResources'
hostingAsset:
$ref: '../hs-hosting/hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetAutoInsert'
required:
- caption
- projectUuid

View File

@@ -94,7 +94,68 @@ components:
- type
- identifier
- caption
- config
additionalProperties: false
HsHostingAssetAutoInsert:
type: object
properties:
parentAssetUuid:
type: string
format: uuid
nullable: true
assignedToAssetUuid:
type: string
format: uuid
type:
$ref: '#/components/schemas/HsHostingAssetType'
identifier:
type: string
minLength: 3
maxLength: 80
nullable: false
caption:
type: string
minLength: 3
maxLength: 80
nullable: false
alarmContactUuid:
type: string
format: uuid
nullable: true
config:
$ref: '#/components/schemas/HsHostingAssetConfiguration'
subHostingAssets:
type: array
items:
$ref: '#/components/schemas/HsHostingAssetSubInsert'
required:
- identifier
additionalProperties: false
HsHostingAssetSubInsert:
type: object
properties:
type:
$ref: '#/components/schemas/HsHostingAssetType'
identifier:
type: string
minLength: 3
maxLength: 80
nullable: false
caption:
type: string
minLength: 3
maxLength: 80
nullable: false
assignedToAssetUuid:
type: string
format: uuid
alarmContactUuid:
type: string
format: uuid
nullable: true
config:
$ref: '#/components/schemas/HsHostingAssetConfiguration'
additionalProperties: false
HsHostingAssetConfiguration:

View File

@@ -1,6 +1,6 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================

View File

@@ -1,6 +1,6 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================

View File

@@ -1,6 +1,6 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================

View File

@@ -1,6 +1,6 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================

View File

@@ -1,6 +1,6 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================

View File

@@ -31,6 +31,20 @@ 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
);
--//
-- ============================================================================
--changeset michael.hoennig:hs-booking-item-MAIN-TABLE-JOURNAL endDelimiter:--//
-- ----------------------------------------------------------------------------

View File

@@ -41,7 +41,7 @@ create table if not exists hs_hosting.asset
config jsonb not null,
alarmContactUuid uuid null references hs_office.contact(uuid) initially deferred,
unique (type, identifier), -- at least as long as we need to be compatible to the legacy system
unique (type, identifier), -- TODO.legacy: at least as long as we need to be compatible to the legacy system
constraint hosting_asset_has_booking_item_or_parent_asset
check (bookingItemUuid is not null or parentAssetUuid is not null or type in ('DOMAIN_SETUP', 'IPV4_NUMBER', 'IPV6_NUMBER'))

View File

@@ -1,6 +1,6 @@
--liquibase formatted sql
-- TODO: These changesets are just for the external remote views to simulate the legacy tables.
-- TODO.legacy: These changesets are just for the external remote views to simulate the legacy tables.
-- Once we don't need the external remote views anymore, create revert changesets.
-- ============================================================================