feat(sprint7): Phase 4 — Integration (SMTP, tier enforcement, WebSocket)
Phase 4 implementation: - 4.1 IONOS SMTP email configuration (production + docker profiles) - 4.2 Portal navigation update (info board, events, forum links) - 4.3 Tier enforcement: PlanTierService (forum=Pro+, info board limits) - 4.4 WebSocket real-time updates (WebSocketEventPublisher) - 4.5 EmailService: notification, event reminder, info board templates + rate limiting - 4.6 Enterprise custom FROM: CustomMailDomain entity, DNS verification, controller New files: - PlanTierService: tier checks for forum/info board/enterprise features - NotificationDispatchService: EMAIL channel dispatch via preferences - WebSocketEventPublisher: STOMP topic push for forum/info board/events - CustomMailDomainService: DNS TXT record verification for custom FROM - MailSettingsController: Enterprise custom domain API endpoints - CustomMailDomain entity + repository - V16 migration: email dispatch index - V17 migration: custom_mail_domains table - Frontend: use-forum-subscription + use-info-board-subscription hooks - Portal navbar: added info board, events, forum navigation items - i18n: added portal nav translations (de + en) Also fixed pre-existing Phase 2.5/3 compilation issues: - Member entity: added userId field - AuditService: added convenience overloads (logEvent, 4-param log) - AuditEventType: added INFO_BOARD_POST_UPDATED, INFO_BOARD_POST_DELETED - QuotaViolationCode: added TIER_UPGRADE_REQUIRED - StaffPermissionChecker: added requirePermission(UserDetails, ...) - TenantContext: added getCurrentTenantId() alias - MemberRepository: added findByUserId, findByClubId, findAllByClubId - EmailServiceTest: updated for new constructor signature
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package de.cannamanage.domain.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Stores custom mail domain configuration for Enterprise tier clubs.
|
||||
* Verified via DNS TXT record: cannamanage-verify={verification_token}
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Entity
|
||||
@Table(name = "custom_mail_domains")
|
||||
public class CustomMailDomain {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "tenant_id", nullable = false, unique = true)
|
||||
private UUID tenantId;
|
||||
|
||||
@Column(name = "from_address", nullable = false)
|
||||
private String fromAddress;
|
||||
|
||||
@Column(name = "domain", nullable = false)
|
||||
private String domain;
|
||||
|
||||
@Column(name = "verification_token", nullable = false, length = 64)
|
||||
private String verificationToken;
|
||||
|
||||
@Column(name = "verified", nullable = false)
|
||||
private boolean verified = false;
|
||||
|
||||
@Column(name = "verified_at")
|
||||
private Instant verifiedAt;
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
public CustomMailDomain(UUID tenantId, String fromAddress, String domain, String verificationToken) {
|
||||
this.tenantId = tenantId;
|
||||
this.fromAddress = fromAddress;
|
||||
this.domain = domain;
|
||||
this.verificationToken = verificationToken;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ import java.util.UUID;
|
||||
)
|
||||
public class Member extends AbstractTenantEntity {
|
||||
|
||||
@Column(name = "user_id")
|
||||
private UUID userId;
|
||||
|
||||
@Column(name = "club_id", nullable = false)
|
||||
private UUID clubId;
|
||||
|
||||
@@ -46,6 +49,9 @@ public class Member extends AbstractTenantEntity {
|
||||
@Column(name = "prevention_officer", nullable = false)
|
||||
private boolean preventionOfficer = false;
|
||||
|
||||
public UUID getUserId() { return userId; }
|
||||
public void setUserId(UUID userId) { this.userId = userId; }
|
||||
|
||||
public UUID getClubId() { return clubId; }
|
||||
public void setClubId(UUID clubId) { this.clubId = clubId; }
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ public final class TenantContext {
|
||||
return CURRENT_TENANT.get();
|
||||
}
|
||||
|
||||
/** Alias for getCurrentTenant() — used by controllers expecting this name. */
|
||||
public static UUID getCurrentTenantId() {
|
||||
return CURRENT_TENANT.get();
|
||||
}
|
||||
|
||||
public static void setCurrentTenant(UUID tenantId) {
|
||||
CURRENT_TENANT.set(tenantId);
|
||||
}
|
||||
|
||||
@@ -48,8 +48,10 @@ public enum AuditEventType {
|
||||
// Sprint 7 — Info Board events
|
||||
INFO_BOARD_POST_CREATED,
|
||||
INFO_BOARD_POST_EDITED,
|
||||
INFO_BOARD_POST_UPDATED,
|
||||
INFO_BOARD_POST_PINNED,
|
||||
INFO_BOARD_POST_ARCHIVED,
|
||||
INFO_BOARD_POST_DELETED,
|
||||
|
||||
// Sprint 7 — Event Calendar events
|
||||
EVENT_CREATED,
|
||||
|
||||
Reference in New Issue
Block a user