341 lines
10 KiB
Go
341 lines
10 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/entitlements"
|
|
)
|
|
|
|
// EntitlementSetViewModel represents an entitlement set for operator template rendering.
|
|
type EntitlementSetViewModel struct {
|
|
SetID string
|
|
Name string
|
|
Description string
|
|
IsActive bool
|
|
RuleCount int
|
|
CreatedAt string
|
|
}
|
|
|
|
// EntitlementSetsData holds data for the entitlement sets list partial.
|
|
type EntitlementSetsData struct {
|
|
EntitlementSets []EntitlementSetViewModel
|
|
Success string
|
|
Error string
|
|
}
|
|
|
|
// EntitlementSetEditData holds data for the entitlement set edit form.
|
|
type EntitlementSetEditData struct {
|
|
EntitlementSet EntitlementSetViewModel
|
|
Success string
|
|
Error string
|
|
}
|
|
|
|
// RuleViewModel represents an entitlement set rule for template rendering.
|
|
type RuleViewModel struct {
|
|
RuleID string
|
|
ResourceKey string
|
|
ResourceValue int64
|
|
ResourcePerUnit bool
|
|
StackingPolicy string
|
|
IsActive bool
|
|
}
|
|
|
|
// EntitlementSetRulesData holds data for the rules detail partial.
|
|
type EntitlementSetRulesData struct {
|
|
EntitlementSet EntitlementSetViewModel
|
|
Rules []RuleViewModel
|
|
ResourceKeys []ResourceKeyOption
|
|
Success string
|
|
Error string
|
|
}
|
|
|
|
// ResourceKeyOption represents a resource key for dropdown selection.
|
|
type ResourceKeyOption struct {
|
|
ResourceKey string
|
|
DisplayName string
|
|
}
|
|
|
|
// GetEntitlementSets handles GET /partials/operator/entitlement-sets
|
|
func (h *OperatorPartialsHandler) GetEntitlementSets(w http.ResponseWriter, r *http.Request) {
|
|
h.renderEntitlementSetsPage(w, r, "", "")
|
|
}
|
|
|
|
// CreateEntitlementSet handles POST /partials/operator/entitlement-sets
|
|
func (h *OperatorPartialsHandler) CreateEntitlementSet(w http.ResponseWriter, r *http.Request) {
|
|
if err := r.ParseForm(); err != nil {
|
|
h.renderEntitlementSetsPage(w, r, "", "Invalid request")
|
|
return
|
|
}
|
|
|
|
name := r.FormValue("name")
|
|
description := r.FormValue("description")
|
|
|
|
if name == "" {
|
|
h.renderEntitlementSetsPage(w, r, "", "Name is required")
|
|
return
|
|
}
|
|
|
|
_, err := h.EntitlementsQ.CreateEntitlementSet(r.Context(), entitlements.CreateEntitlementSetParams{
|
|
Name: name,
|
|
Description: sql.NullString{String: description, Valid: description != ""},
|
|
IsActive: true,
|
|
})
|
|
if err != nil {
|
|
h.Logger.Error("failed to create entitlement set", slog.Any("error", err))
|
|
h.renderEntitlementSetsPage(w, r, "", "Failed to create entitlement set: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Signal dependent tabs (Products, Grants) to re-fetch — they show entitlement set dropdowns
|
|
w.Header().Set("HX-Trigger", "entitlementMutation")
|
|
h.renderEntitlementSetsPage(w, r, "Entitlement set created successfully.", "")
|
|
}
|
|
|
|
// GetEntitlementSetEdit handles GET /partials/operator/entitlement-sets/{setID}/edit
|
|
func (h *OperatorPartialsHandler) GetEntitlementSetEdit(w http.ResponseWriter, r *http.Request) {
|
|
setID := r.PathValue("setID")
|
|
h.renderEntitlementSetEditPage(w, r, setID, "", "")
|
|
}
|
|
|
|
// UpdateEntitlementSet handles PUT /partials/operator/entitlement-sets/{setID}
|
|
func (h *OperatorPartialsHandler) UpdateEntitlementSet(w http.ResponseWriter, r *http.Request) {
|
|
setID := r.PathValue("setID")
|
|
if err := r.ParseForm(); err != nil {
|
|
h.renderEntitlementSetEditPage(w, r, setID, "", "Invalid request")
|
|
return
|
|
}
|
|
|
|
name := r.FormValue("name")
|
|
description := r.FormValue("description")
|
|
|
|
if name == "" {
|
|
h.renderEntitlementSetEditPage(w, r, setID, "", "Name is required")
|
|
return
|
|
}
|
|
|
|
isActive := r.FormValue("is_active") == "true"
|
|
|
|
_, err := h.EntitlementsQ.UpdateEntitlementSet(r.Context(), entitlements.UpdateEntitlementSetParams{
|
|
SetID: setID,
|
|
Name: name,
|
|
Description: sql.NullString{String: description, Valid: description != ""},
|
|
IsActive: isActive,
|
|
})
|
|
if err != nil {
|
|
h.Logger.Error("failed to update entitlement set", slog.Any("error", err))
|
|
h.renderEntitlementSetEditPage(w, r, setID, "", "Failed to update entitlement set: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Signal dependent tabs (Products, Grants) to re-fetch — they show entitlement set dropdowns
|
|
w.Header().Set("HX-Trigger", "entitlementMutation")
|
|
h.renderEntitlementSetsPage(w, r, "Entitlement set updated successfully.", "")
|
|
}
|
|
|
|
// GetEntitlementSetRules handles GET /partials/operator/entitlement-sets/{setID}/rules
|
|
func (h *OperatorPartialsHandler) GetEntitlementSetRules(w http.ResponseWriter, r *http.Request) {
|
|
setID := r.PathValue("setID")
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "", "")
|
|
}
|
|
|
|
// CreateEntitlementSetRule handles POST /partials/operator/entitlement-sets/{setID}/rules
|
|
func (h *OperatorPartialsHandler) CreateEntitlementSetRule(w http.ResponseWriter, r *http.Request) {
|
|
setID := r.PathValue("setID")
|
|
if err := r.ParseForm(); err != nil {
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "", "Invalid request")
|
|
return
|
|
}
|
|
|
|
resourceKey := r.FormValue("resource_key")
|
|
resourceValueStr := r.FormValue("resource_value")
|
|
stackingPolicy := r.FormValue("stacking_policy")
|
|
resourcePerUnit := r.FormValue("resource_per_unit") == "true"
|
|
|
|
if resourceKey == "" || resourceValueStr == "" || stackingPolicy == "" {
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "", "Resource key, value, and stacking policy are required")
|
|
return
|
|
}
|
|
|
|
resourceValue, err := strconv.ParseInt(resourceValueStr, 10, 64)
|
|
if err != nil {
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "", "Invalid resource value")
|
|
return
|
|
}
|
|
|
|
_, err = h.EntitlementsQ.CreateEntitlementSetRule(r.Context(), entitlements.CreateEntitlementSetRuleParams{
|
|
SetID: setID,
|
|
RuleType: "limit",
|
|
ResourceKey: sql.NullString{String: resourceKey, Valid: true},
|
|
ResourceValue: sql.NullInt64{Int64: resourceValue, Valid: true},
|
|
ResourcePerUnit: sql.NullBool{Bool: resourcePerUnit, Valid: true},
|
|
StackingPolicy: sql.NullString{String: stackingPolicy, Valid: true},
|
|
})
|
|
if err != nil {
|
|
h.Logger.Error("failed to create rule", slog.Any("error", err))
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "", "Failed to create rule: "+err.Error())
|
|
return
|
|
}
|
|
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "Rule added successfully.", "")
|
|
}
|
|
|
|
// DeleteEntitlementSetRule handles DELETE /partials/operator/entitlement-sets/{setID}/rules/{ruleID}
|
|
func (h *OperatorPartialsHandler) DeleteEntitlementSetRule(w http.ResponseWriter, r *http.Request) {
|
|
setID := r.PathValue("setID")
|
|
ruleID := r.PathValue("ruleID")
|
|
|
|
err := h.EntitlementsQ.DeleteEntitlementSetRule(r.Context(), ruleID)
|
|
if err != nil {
|
|
h.Logger.Error("failed to delete rule", slog.Any("error", err))
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "", "Failed to delete rule: "+err.Error())
|
|
return
|
|
}
|
|
|
|
h.renderEntitlementSetRulesPage(w, r, setID, "Rule deleted.", "")
|
|
}
|
|
|
|
// Rendering helpers
|
|
|
|
func (h *OperatorPartialsHandler) renderEntitlementSetsPage(w http.ResponseWriter, r *http.Request, success string, errMsg string) {
|
|
data := EntitlementSetsData{
|
|
Success: success,
|
|
Error: errMsg,
|
|
}
|
|
|
|
if errMsg == "" {
|
|
sets, err := h.EntitlementsQ.ListEntitlementSets(r.Context())
|
|
if err != nil {
|
|
h.Logger.Error("failed to list entitlement sets", slog.Any("error", err))
|
|
data.Error = "Failed to load entitlement sets"
|
|
} else {
|
|
data.EntitlementSets = make([]EntitlementSetViewModel, len(sets))
|
|
for i, s := range sets {
|
|
ruleCount := 0
|
|
rules, err := h.EntitlementsQ.GetActiveRulesBySetID(r.Context(), s.SetID)
|
|
if err == nil {
|
|
ruleCount = len(rules)
|
|
}
|
|
desc := ""
|
|
if s.Description.Valid {
|
|
desc = s.Description.String
|
|
}
|
|
data.EntitlementSets[i] = EntitlementSetViewModel{
|
|
SetID: s.SetID,
|
|
Name: s.Name,
|
|
Description: desc,
|
|
IsActive: s.IsActive,
|
|
RuleCount: ruleCount,
|
|
CreatedAt: s.CreatedAt.Format("Jan 2, 2006"),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
h.Templates.Render(w, "operator_entitlement_sets.html", data)
|
|
}
|
|
|
|
func (h *OperatorPartialsHandler) renderEntitlementSetEditPage(w http.ResponseWriter, r *http.Request, setID string, success string, errMsg string) {
|
|
data := EntitlementSetEditData{
|
|
Success: success,
|
|
Error: errMsg,
|
|
}
|
|
|
|
es, err := h.EntitlementsQ.GetEntitlementSetByID(r.Context(), setID)
|
|
if err != nil {
|
|
h.Logger.Error("failed to get entitlement set", slog.Any("error", err))
|
|
h.renderEntitlementSetsPage(w, r, "", "Entitlement set not found")
|
|
return
|
|
}
|
|
|
|
desc := ""
|
|
if es.Description.Valid {
|
|
desc = es.Description.String
|
|
}
|
|
data.EntitlementSet = EntitlementSetViewModel{
|
|
SetID: es.SetID,
|
|
Name: es.Name,
|
|
Description: desc,
|
|
IsActive: es.IsActive,
|
|
CreatedAt: es.CreatedAt.Format("Jan 2, 2006"),
|
|
}
|
|
|
|
h.Templates.Render(w, "operator_entitlement_set_edit.html", data)
|
|
}
|
|
|
|
func (h *OperatorPartialsHandler) renderEntitlementSetRulesPage(w http.ResponseWriter, r *http.Request, setID string, success string, errMsg string) {
|
|
data := EntitlementSetRulesData{
|
|
Success: success,
|
|
Error: errMsg,
|
|
}
|
|
|
|
es, err := h.EntitlementsQ.GetEntitlementSetByID(r.Context(), setID)
|
|
if err != nil {
|
|
h.Logger.Error("failed to get entitlement set", slog.Any("error", err))
|
|
h.renderEntitlementSetsPage(w, r, "", "Entitlement set not found")
|
|
return
|
|
}
|
|
|
|
desc := ""
|
|
if es.Description.Valid {
|
|
desc = es.Description.String
|
|
}
|
|
data.EntitlementSet = EntitlementSetViewModel{
|
|
SetID: es.SetID,
|
|
Name: es.Name,
|
|
Description: desc,
|
|
IsActive: es.IsActive,
|
|
}
|
|
|
|
rules, err := h.EntitlementsQ.GetActiveRulesBySetID(r.Context(), setID)
|
|
if err != nil {
|
|
h.Logger.Error("failed to list rules", slog.Any("error", err))
|
|
} else {
|
|
data.Rules = make([]RuleViewModel, len(rules))
|
|
for i, rule := range rules {
|
|
rk := ""
|
|
if rule.ResourceKey.Valid {
|
|
rk = rule.ResourceKey.String
|
|
}
|
|
var rv int64
|
|
if rule.ResourceValue.Valid {
|
|
rv = rule.ResourceValue.Int64
|
|
}
|
|
rpu := false
|
|
if rule.ResourcePerUnit.Valid {
|
|
rpu = rule.ResourcePerUnit.Bool
|
|
}
|
|
sp := ""
|
|
if rule.StackingPolicy.Valid {
|
|
sp = rule.StackingPolicy.String
|
|
}
|
|
data.Rules[i] = RuleViewModel{
|
|
RuleID: rule.RuleID,
|
|
ResourceKey: rk,
|
|
ResourceValue: rv,
|
|
ResourcePerUnit: rpu,
|
|
StackingPolicy: sp,
|
|
IsActive: rule.IsActive,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load resource keys for dropdown
|
|
resourceKeys, err := h.EntitlementsQ.ListResourceKeys(r.Context())
|
|
if err != nil {
|
|
h.Logger.Error("failed to list resource keys", slog.Any("error", err))
|
|
} else {
|
|
data.ResourceKeys = make([]ResourceKeyOption, len(resourceKeys))
|
|
for i, rk := range resourceKeys {
|
|
data.ResourceKeys[i] = ResourceKeyOption{
|
|
ResourceKey: rk.ResourceKey,
|
|
DisplayName: rk.DisplayName,
|
|
}
|
|
}
|
|
}
|
|
|
|
h.Templates.Render(w, "operator_entitlement_set_rules.html", data)
|
|
}
|