diff --git a/src/main/java/net/siegeln/cameleer/saas/audit/AuditAction.java b/src/main/java/net/siegeln/cameleer/saas/audit/AuditAction.java index 2281748..874cbff 100644 --- a/src/main/java/net/siegeln/cameleer/saas/audit/AuditAction.java +++ b/src/main/java/net/siegeln/cameleer/saas/audit/AuditAction.java @@ -6,5 +6,6 @@ public enum AuditAction { APP_CREATE, APP_DEPLOY, APP_PROMOTE, APP_ROLLBACK, APP_SCALE, APP_STOP, APP_DELETE, SECRET_CREATE, SECRET_READ, SECRET_UPDATE, SECRET_DELETE, SECRET_ROTATE, CONFIG_UPDATE, - TEAM_INVITE, TEAM_REMOVE, TEAM_ROLE_CHANGE + TEAM_INVITE, TEAM_REMOVE, TEAM_ROLE_CHANGE, + LICENSE_GENERATE, LICENSE_REVOKE } diff --git a/src/main/java/net/siegeln/cameleer/saas/license/LicenseEntity.java b/src/main/java/net/siegeln/cameleer/saas/license/LicenseEntity.java new file mode 100644 index 0000000..d0a1639 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/license/LicenseEntity.java @@ -0,0 +1,78 @@ +package net.siegeln.cameleer.saas.license; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +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 = "licenses") +public class LicenseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "tenant_id", nullable = false) + private UUID tenantId; + + @Column(name = "tier", nullable = false, length = 20) + private String tier; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "features", nullable = false, columnDefinition = "jsonb") + private Map features; + + @JdbcTypeCode(SqlTypes.JSON) + @Column(name = "limits", nullable = false, columnDefinition = "jsonb") + private Map limits; + + @Column(name = "issued_at", nullable = false) + private Instant issuedAt; + + @Column(name = "expires_at", nullable = false) + private Instant expiresAt; + + @Column(name = "revoked_at") + private Instant revokedAt; + + @Column(name = "token", nullable = false, columnDefinition = "text") + private String token; + + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @PrePersist + protected void onCreate() { + if (createdAt == null) createdAt = Instant.now(); + if (issuedAt == null) issuedAt = Instant.now(); + } + + public UUID getId() { return id; } + public UUID getTenantId() { return tenantId; } + public void setTenantId(UUID tenantId) { this.tenantId = tenantId; } + public String getTier() { return tier; } + public void setTier(String tier) { this.tier = tier; } + public Map getFeatures() { return features; } + public void setFeatures(Map features) { this.features = features; } + public Map getLimits() { return limits; } + public void setLimits(Map limits) { this.limits = limits; } + public Instant getIssuedAt() { return issuedAt; } + public void setIssuedAt(Instant issuedAt) { this.issuedAt = issuedAt; } + public Instant getExpiresAt() { return expiresAt; } + public void setExpiresAt(Instant expiresAt) { this.expiresAt = expiresAt; } + public Instant getRevokedAt() { return revokedAt; } + public void setRevokedAt(Instant revokedAt) { this.revokedAt = revokedAt; } + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + public Instant getCreatedAt() { return createdAt; } +} diff --git a/src/main/java/net/siegeln/cameleer/saas/license/LicenseRepository.java b/src/main/java/net/siegeln/cameleer/saas/license/LicenseRepository.java new file mode 100644 index 0000000..e4067f8 --- /dev/null +++ b/src/main/java/net/siegeln/cameleer/saas/license/LicenseRepository.java @@ -0,0 +1,14 @@ +package net.siegeln.cameleer.saas.license; + +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 LicenseRepository extends JpaRepository { + List findByTenantIdOrderByCreatedAtDesc(UUID tenantId); + Optional findFirstByTenantIdAndRevokedAtIsNullOrderByCreatedAtDesc(UUID tenantId); +} diff --git a/src/main/resources/db/migration/V006__create_licenses.sql b/src/main/resources/db/migration/V006__create_licenses.sql new file mode 100644 index 0000000..c7984f7 --- /dev/null +++ b/src/main/resources/db/migration/V006__create_licenses.sql @@ -0,0 +1,15 @@ +CREATE TABLE licenses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + tier VARCHAR(20) NOT NULL, + features JSONB NOT NULL DEFAULT '{}', + limits JSONB NOT NULL DEFAULT '{}', + issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + expires_at TIMESTAMPTZ NOT NULL, + revoked_at TIMESTAMPTZ, + token TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_licenses_tenant_id ON licenses (tenant_id); +CREATE INDEX idx_licenses_expires_at ON licenses (expires_at);