feat: add license entity, repository, and database migration

Licenses table linked to tenants with JSONB features/limits, Ed25519
signed token storage, and revocation support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-04 14:56:52 +02:00
parent c1cae25db7
commit a74894e0f1
4 changed files with 109 additions and 1 deletions

View File

@@ -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
}

View File

@@ -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<String, Object> features;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "limits", nullable = false, columnDefinition = "jsonb")
private Map<String, Object> 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<String, Object> getFeatures() { return features; }
public void setFeatures(Map<String, Object> features) { this.features = features; }
public Map<String, Object> getLimits() { return limits; }
public void setLimits(Map<String, Object> 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; }
}

View File

@@ -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<LicenseEntity, UUID> {
List<LicenseEntity> findByTenantIdOrderByCreatedAtDesc(UUID tenantId);
Optional<LicenseEntity> findFirstByTenantIdAndRevokedAtIsNullOrderByCreatedAtDesc(UUID tenantId);
}

View File

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