Introduce a commercial license option alongside AGPL-3.0-only, require a CLA for contributors, and document the terms in COMMERCIAL.md and NOTICE. Add a script to stamp SPDX headers on Go files and apply it across the tree.
65 lines
3.0 KiB
Go
65 lines
3.0 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package server
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/instance"
|
|
)
|
|
|
|
// GetSetupPage handles GET /operator/setup — the persistent, revisitable
|
|
// setup checklist (design D12 / operator-setup-checklist). Unlike a wizard,
|
|
// this page always renders, complete or not, and never gates or sequences
|
|
// any other operator surface; an operator can leave any step undone and
|
|
// keep using the rest of the console. It is the checklist's only renderer:
|
|
// the operator overview carries a dismissible summary banner instead of
|
|
// embedding the step list (round 2; see operator.html's setup-banner
|
|
// region and SetupState.ShowBanner).
|
|
//
|
|
// It shares OperatorHandler with the landing surface (not
|
|
// OperatorPartialsHandler) because both surfaces derive from the same
|
|
// DeriveSetupState so they can never disagree about what is or is not done.
|
|
func (h *OperatorHandler) GetSetupPage(w http.ResponseWriter, r *http.Request) {
|
|
page := BuildOperatorPageData(r, h.AuthConfig, h.Database, h.Logger)
|
|
page.IAPosition = "runtime:setup"
|
|
// No sidebar entry names "setup" — it is reached from the System
|
|
// section header's "Setup" action, not the flat capability list — so
|
|
// leaving ActiveCapability unmatched lights nothing up, same as
|
|
// GetPersonPage's sidebar-less person detail page.
|
|
page.ActiveCapability = "setup"
|
|
page.BodyTemplate = "operator_setup.html"
|
|
|
|
page.BodyData = h.DeriveSetupState(r.Context())
|
|
|
|
h.Templates.Render(w, "operator.html", page)
|
|
}
|
|
|
|
// DismissSetupBanner handles POST /partials/operator/setup/dismiss — the
|
|
// operator overview's setup-banner close button. Records the dismissal
|
|
// deployment-wide (design D12/D28: the instance setting
|
|
// instance.SetupBannerDismissed, core.instance_settings — not a cookie, not
|
|
// per-operator) and responds with an empty 200 body so the banner's own
|
|
// hx-swap="outerHTML" removes it with nothing left in its place.
|
|
//
|
|
// A method on OperatorPartialsHandler, not OperatorHandler, because the
|
|
// route lives among the other operator HTMX mutation routes
|
|
// (operator_partials.go); it builds its own instance.Store from the
|
|
// existing Database field rather than adding one, matching how it already
|
|
// builds its own integration.Queries per request (GetIntegrationsPage).
|
|
func (h *OperatorPartialsHandler) DismissSetupBanner(w http.ResponseWriter, r *http.Request) {
|
|
page := h.buildOperatorPageData(r)
|
|
store := instance.NewStore(h.Database)
|
|
// updatedBy mirrors the config-override settings write path
|
|
// (operator_integration_settings.go PostIntegrationSetting): the
|
|
// signed-in operator's email, or unset (NULL) when unavailable.
|
|
if err := store.SetBool(r.Context(), instance.SetupBannerDismissed, true, page.Email); err != nil {
|
|
h.Logger.Error("failed to dismiss setup banner", slog.Any("error", err))
|
|
http.Error(w, "Failed to dismiss the setup banner.", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|