mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
* feat(setup): first-run wizard — admin, GitHub App, tunnel, done - GET /api/v1/setup/status (public): has_users, needs_setup, has_github_app, has_tunnel. app_settings.setup_completed flag; installs with existing projects auto-complete so upgrades never see the wizard. - POST /api/v1/setup/complete records the flag. - /setup page: progress rail + four steps — create admin (links into the bootstrap sign-up with redirect back), GitHub App manifest flow, optional Cloudflare tunnel token, finish. Every optional step is skippable; nothing blocks. - PlatformShell redirects signed-in users to /setup while needs_setup. - The GitHub App callback honors a sessionStorage return marker set by the wizard so provisioning resumes at /setup instead of /settings. - OpenAPI documents both endpoints. Verified live: status reports auto-completed on this install, complete persists the flag, response shape matches the client. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(setup): wizard step-state coverage Five cases across the state machine: fresh-install account prompt, sign-in resume, git-provider step, tunnel step, and finish. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
package api
|
|
|
|
// First-run setup wizard state. GET /setup/status is public so the web
|
|
// shell can redirect fresh installs before a session exists; the flag
|
|
// lives in app_settings so it survives restarts.
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"containr/internal/database"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
const settingSetupCompleted = "setup_completed"
|
|
|
|
// setupComplete resolves the flag. Installs that predate the wizard
|
|
// (users + projects already exist) auto-complete — nobody who has been
|
|
// running Containr for months should get a welcome screen.
|
|
func setupComplete(db *database.DB, hasUsers bool) bool {
|
|
if strings.EqualFold(settingValue(db, settingSetupCompleted, "", ""), "true") {
|
|
return true
|
|
}
|
|
if !hasUsers {
|
|
return false
|
|
}
|
|
var projects int
|
|
if err := db.QueryRow(`SELECT COUNT(*) FROM projects`).Scan(&projects); err == nil && projects > 0 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GET /api/v1/setup/status — public: fresh installs hit it before any
|
|
// session exists.
|
|
func handleSetupStatus(c *gin.Context) {
|
|
db, ok := c.MustGet("db").(*database.DB)
|
|
if !ok {
|
|
respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", "Database connection not available")
|
|
return
|
|
}
|
|
count, err := countLocalUsers(db)
|
|
if err != nil {
|
|
respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", "Failed to inspect setup state")
|
|
return
|
|
}
|
|
hasUsers := count > 0
|
|
completed := setupComplete(db, hasUsers)
|
|
tunnelToken, _ := resolvedTunnelToken(db)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"has_users": hasUsers,
|
|
"setup_completed": completed,
|
|
"needs_setup": !completed,
|
|
"has_github_app": githubAppConfigured(db),
|
|
"has_tunnel": tunnelToken != "",
|
|
"signup_enabled": signupEnabled(db),
|
|
})
|
|
}
|
|
|
|
// POST /api/v1/setup/complete — marks the wizard done. Open to any
|
|
// authenticated user: the first account is the owner, and the flag only
|
|
// suppresses a banner.
|
|
func handleSetupComplete(c *gin.Context) {
|
|
if _, ok := requireAuthenticatedUserID(c); !ok {
|
|
return
|
|
}
|
|
db, ok := c.MustGet("db").(*database.DB)
|
|
if !ok {
|
|
respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", "Database connection not available")
|
|
return
|
|
}
|
|
if err := setSetting(db, settingSetupCompleted, "true", false); err != nil {
|
|
respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", "Failed to save setup state")
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "Setup completed"})
|
|
}
|