diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantEntity.java b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantEntity.java new file mode 100644 index 0000000..351a786 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantEntity.java @@ -0,0 +1,92 @@ +package net.siegeln.cameleer.saas.tenant; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Table; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +@Entity +@Table(name = "tenants") +public class TenantEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "slug", nullable = false, unique = true, length = 100) + private String slug; + + @Enumerated(EnumType.STRING) + @Column(name = "tier", nullable = false, length = 20) + private Tier tier = Tier.LOW; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false, length = 20) + private TenantStatus status = TenantStatus.PROVISIONING; + + @Column(name = "logto_org_id") + private String logtoOrgId; + + @Column(name = "stripe_customer_id") + private String stripeCustomerId; + + @Column(name = "stripe_subscription_id") + private String stripeSubscriptionId; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "settings", columnDefinition = "jsonb") + private Map settings = Map.of(); + + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + + @PrePersist + protected void onCreate() { + Instant now = Instant.now(); + if (createdAt == null) createdAt = now; + if (updatedAt == null) updatedAt = now; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = Instant.now(); + } + + public UUID getId() { return id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getSlug() { return slug; } + public void setSlug(String slug) { this.slug = slug; } + public Tier getTier() { return tier; } + public void setTier(Tier tier) { this.tier = tier; } + public TenantStatus getStatus() { return status; } + public void setStatus(TenantStatus status) { this.status = status; } + public String getLogtoOrgId() { return logtoOrgId; } + public void setLogtoOrgId(String logtoOrgId) { this.logtoOrgId = logtoOrgId; } + public String getStripeCustomerId() { return stripeCustomerId; } + public void setStripeCustomerId(String stripeCustomerId) { this.stripeCustomerId = stripeCustomerId; } + public String getStripeSubscriptionId() { return stripeSubscriptionId; } + public void setStripeSubscriptionId(String stripeSubscriptionId) { this.stripeSubscriptionId = stripeSubscriptionId; } + public Map getSettings() { return settings; } + public void setSettings(Map settings) { this.settings = settings; } + public Instant getCreatedAt() { return createdAt; } + public Instant getUpdatedAt() { return updatedAt; } +} diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantRepository.java b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantRepository.java new file mode 100644 index 0000000..30e355d --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantRepository.java @@ -0,0 +1,16 @@ +package net.siegeln.cameleer.saas.tenant; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface TenantRepository extends JpaRepository { + Optional findBySlug(String slug); + Optional findByLogtoOrgId(String logtoOrgId); + List findByStatus(TenantStatus status); + boolean existsBySlug(String slug); +} diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/TenantStatus.java b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantStatus.java new file mode 100644 index 0000000..c97fed3 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/TenantStatus.java @@ -0,0 +1,5 @@ +package net.siegeln.cameleer.saas.tenant; + +public enum TenantStatus { + PROVISIONING, ACTIVE, SUSPENDED, DELETED +} diff --git a/src/main/java/net/siegeln/cameleer/saas/tenant/Tier.java b/src/main/java/net/siegeln/cameleer/saas/tenant/Tier.java new file mode 100644 index 0000000..eb04075 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/tenant/Tier.java @@ -0,0 +1,5 @@ +package net.siegeln.cameleer.saas.tenant; + +public enum Tier { + LOW, MID, HIGH, BUSINESS +} diff --git a/src/main/resources/db/migration/V005__create_tenants.sql b/src/main/resources/db/migration/V005__create_tenants.sql new file mode 100644 index 0000000..80746f9 --- /dev/null +++ b/src/main/resources/db/migration/V005__create_tenants.sql @@ -0,0 +1,17 @@ +CREATE TABLE tenants ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + slug VARCHAR(100) NOT NULL UNIQUE, + tier VARCHAR(20) NOT NULL DEFAULT 'LOW', + status VARCHAR(20) NOT NULL DEFAULT 'PROVISIONING', + logto_org_id VARCHAR(255), + stripe_customer_id VARCHAR(255), + stripe_subscription_id VARCHAR(255), + settings JSONB NOT NULL DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_tenants_slug ON tenants (slug); +CREATE INDEX idx_tenants_status ON tenants (status); +CREATE INDEX idx_tenants_logto_org_id ON tenants (logto_org_id);