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:
@@ -6,5 +6,6 @@ public enum AuditAction {
|
|||||||
APP_CREATE, APP_DEPLOY, APP_PROMOTE, APP_ROLLBACK, APP_SCALE, APP_STOP, APP_DELETE,
|
APP_CREATE, APP_DEPLOY, APP_PROMOTE, APP_ROLLBACK, APP_SCALE, APP_STOP, APP_DELETE,
|
||||||
SECRET_CREATE, SECRET_READ, SECRET_UPDATE, SECRET_DELETE, SECRET_ROTATE,
|
SECRET_CREATE, SECRET_READ, SECRET_UPDATE, SECRET_DELETE, SECRET_ROTATE,
|
||||||
CONFIG_UPDATE,
|
CONFIG_UPDATE,
|
||||||
TEAM_INVITE, TEAM_REMOVE, TEAM_ROLE_CHANGE
|
TEAM_INVITE, TEAM_REMOVE, TEAM_ROLE_CHANGE,
|
||||||
|
LICENSE_GENERATE, LICENSE_REVOKE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
15
src/main/resources/db/migration/V006__create_licenses.sql
Normal file
15
src/main/resources/db/migration/V006__create_licenses.sql
Normal 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);
|
||||||
Reference in New Issue
Block a user