feat: add tenant entity, repository, and database migration

Tenants table with slug, tier (LOW/MID/HIGH/BUSINESS), status
(PROVISIONING/ACTIVE/SUSPENDED/DELETED), Logto org reference, and
Stripe IDs.
This commit is contained in:
hsiegeln
2026-04-04 14:53:51 +02:00
parent 0a2d5970e4
commit 119034307c
5 changed files with 135 additions and 0 deletions

View File

@@ -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<String, Object> 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<String, Object> getSettings() { return settings; }
public void setSettings(Map<String, Object> settings) { this.settings = settings; }
public Instant getCreatedAt() { return createdAt; }
public Instant getUpdatedAt() { return updatedAt; }
}

View File

@@ -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<TenantEntity, UUID> {
Optional<TenantEntity> findBySlug(String slug);
Optional<TenantEntity> findByLogtoOrgId(String logtoOrgId);
List<TenantEntity> findByStatus(TenantStatus status);
boolean existsBySlug(String slug);
}

View File

@@ -0,0 +1,5 @@
package net.siegeln.cameleer.saas.tenant;
public enum TenantStatus {
PROVISIONING, ACTIVE, SUSPENDED, DELETED
}

View File

@@ -0,0 +1,5 @@
package net.siegeln.cameleer.saas.tenant;
public enum Tier {
LOW, MID, HIGH, BUSINESS
}

View File

@@ -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);