Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
@@ -1,13 +1,5 @@
package com.profiletailors.smp.platformadmin.application.contracts

import org.springframework.modulith.NamedInterface

/**
* Builds the fully-formed accept URL with the raw token embedded. Centralised so the URL
* shape stays consistent across invite/resend flows and so the raw token never has to be
* mixed into controller code.
*/
@NamedInterface("contracts")
fun interface AcceptUrlTemplate {
fun build(rawToken: String): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package com.profiletailors.smp.platformadmin.application.contracts;
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package com.profiletailors.smp.platformadmin.domain

import com.profiletailors.common.domain.bus.event.BaseDomainEvent
import org.springframework.modulith.NamedInterface
import java.util.UUID

@NamedInterface("domain")
data class DirectInvitationResent(
val invitationId: UUID,
val operatorPrincipalId: UUID,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
package com.profiletailors.smp.platformadmin.domain

import com.profiletailors.common.domain.bus.event.BaseDomainEvent
import org.springframework.modulith.NamedInterface
import java.util.UUID

/**
* Domain event published when a platform operator issues a new invitation to a waitlist lead.
*
* This event contains the non-sensitive invitation data required by notification delivery.
*
* This replaces the previous `InvitationCreated` event as part of establishing clean architectural
* boundaries between the Invitation lifecycle (ACTIVE/ACCEPTED/EXPIRED/REVOKED) and Notification
* delivery lifecycle (PENDING/SENT/FAILED).
*
* @property invitationId canonical invitation identifier (UUID)
* @property recipientEmail normalized email address of the invitee
* @property workspaceName human-readable workspace name used in email copy
* @property locale optional BCP-47 locale code (e.g. "en", "es") for template selection
* @property rawToken ephemeral bearer token used by the notification consumer to build acceptance URLs
*/
@NamedInterface("domain")
data class InvitationIssued(
val invitationId: UUID,
val recipientEmail: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package com.profiletailors.smp.platformadmin.domain;
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

/**
* Architecture tests that enforce hexagonal layer boundaries in the smp module.
*
* Rules:
* - domain: pure Kotlin, no Spring, no upward dependencies
* - application: depends on domain only (within smp packages)
* - infrastructure: may depend on domain and application
* - every bounded context exposes domain, application, and infrastructure packages
*
* Bounded contexts are auto-discovered from the package tree so that
* adding a new context automatically includes it in the validation.
*/
internal class HexagonalArchTest {

private lateinit var importedClasses: JavaClasses
Expand Down Expand Up @@ -54,12 +42,6 @@ internal class HexagonalArchTest {
.haveSimpleNameNotEndingWith("ModuleMetadata")
.and()
.haveSimpleNameNotEndingWith("package-info")
.and()
.haveSimpleNameNotContaining("InvitationIssued")
.and()
.haveSimpleNameNotContaining("DirectInvitationResent")
.and()
.haveSimpleNameNotContaining("DomainLayerExports")
.should()
.dependOnClassesThat()
.resideInAnyPackage("org.springframework..")
Expand Down Expand Up @@ -119,10 +101,6 @@ internal class HexagonalArchTest {
.check(importedClasses)
}

/**
* Guards against the real violation: application layer using Spring R2DBC, HTTP, or Security
* imports directly — bypassing the domain port/abstraction layer.
*/
@Test
fun applicationLayerShouldNotDependOnSpringR2dbcHttpOrSecurity() {
ArchRuleDefinition.noClasses()
Expand All @@ -143,9 +121,6 @@ internal class HexagonalArchTest {
.check(importedClasses)
}

/**
* Guards against reactive/infrastructure imports leaking into application via coroutine adapters.
*/
@Test
fun applicationLayerShouldNotDependOnReactorOrCoroutinesReactor() {
ArchRuleDefinition.noClasses()
Expand All @@ -164,9 +139,6 @@ internal class HexagonalArchTest {
.check(importedClasses)
}

/**
* Guards against Spring Security base classes leaking into application exceptions.
*/
@Test
fun applicationLayerShouldNotExtendSpringSecurityClasses() {
ArchRuleDefinition.noClasses()
Expand All @@ -182,9 +154,6 @@ internal class HexagonalArchTest {
.check(importedClasses)
}

/**
* Guards domain from any infrastructure framework — even narrower than the existing Spring check.
*/
@Test
fun domainLayerShouldNotDependOnInfrastructureFrameworks() {
ArchRuleDefinition.noClasses()
Expand All @@ -194,12 +163,6 @@ internal class HexagonalArchTest {
.haveSimpleNameNotEndingWith("ModuleMetadata")
.and()
.haveSimpleNameNotEndingWith("package-info")
.and()
.haveSimpleNameNotContaining("InvitationIssued")
.and()
.haveSimpleNameNotContaining("DirectInvitationResent")
.and()
.haveSimpleNameNotContaining("DomainLayerExports")
.should()
.dependOnClassesThat()
.resideInAnyPackage(
Expand Down
Loading