refactor(server): move server command into its own package and executable
This commit is contained in:
7
Makefile
7
Makefile
@@ -4,10 +4,13 @@ all: gen start open
|
||||
gen:
|
||||
@templ generate
|
||||
|
||||
build:
|
||||
@go build -o nebula ./cmd/server
|
||||
|
||||
start:
|
||||
@go run main.go
|
||||
@go run ./cmd/server
|
||||
|
||||
open:
|
||||
@open http://localhost:8080
|
||||
|
||||
.PHONY: gen start
|
||||
.PHONY: gen build start open all
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"nebula/models"
|
||||
"nebula/views"
|
||||
)
|
||||
|
||||
func RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /", handleWelcome)
|
||||
mux.HandleFunc("GET /welcome", handleWelcome)
|
||||
mux.HandleFunc("GET /welcome/step/{step}", handleWelcomeStep)
|
||||
|
||||
mux.HandleFunc("GET /register", handleRegister)
|
||||
mux.HandleFunc("GET /register/step/{step}", handleRegisterStep)
|
||||
mux.HandleFunc("GET /register/capabilities", handleRegisterCapabilities)
|
||||
mux.HandleFunc("POST /register/verify-code", handleRegisterVerifyCode)
|
||||
|
||||
mux.HandleFunc("GET /login", handleLogin)
|
||||
mux.HandleFunc("GET /login/step/{step}", handleLoginStep)
|
||||
mux.HandleFunc("GET /login/qr-status", handleLoginQRStatus)
|
||||
|
||||
mux.HandleFunc("GET /authorize", handleAuthorize)
|
||||
mux.HandleFunc("POST /authorize/approve", handleAuthorizeApprove)
|
||||
mux.HandleFunc("POST /authorize/deny", handleAuthorizeDeny)
|
||||
|
||||
mux.HandleFunc("GET /dashboard", handleDashboard)
|
||||
|
||||
mux.HandleFunc("GET /settings", handleSettings)
|
||||
}
|
||||
|
||||
// handleWelcome renders the full welcome page at step 1
|
||||
func handleWelcome(w http.ResponseWriter, r *http.Request) {
|
||||
views.WelcomePage(1).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
// handleWelcomeStep handles HTMX partial updates for step navigation
|
||||
func handleWelcomeStep(w http.ResponseWriter, r *http.Request) {
|
||||
stepStr := r.PathValue("step")
|
||||
step, err := strconv.Atoi(stepStr)
|
||||
if err != nil || step < 1 || step > 3 {
|
||||
step = 1
|
||||
}
|
||||
|
||||
// Check if this is an HTMX request
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
// Return step content with OOB stepper update (HTMX 4 pattern)
|
||||
views.WelcomeStepWithStepper(step).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
|
||||
views.WelcomePage(step).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
state := models.RegisterState{Step: 1}
|
||||
views.RegisterPage(state).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleRegisterStep(w http.ResponseWriter, r *http.Request) {
|
||||
stepStr := r.PathValue("step")
|
||||
step, err := strconv.Atoi(stepStr)
|
||||
if err != nil || step < 1 || step > 3 {
|
||||
step = 1
|
||||
}
|
||||
|
||||
method := r.URL.Query().Get("method")
|
||||
if method == "" {
|
||||
method = "passkey"
|
||||
}
|
||||
|
||||
state := models.RegisterState{Step: step, Method: method}
|
||||
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
views.RegisterStepWithStepper(state).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
|
||||
views.RegisterPage(state).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleRegisterCapabilities(w http.ResponseWriter, r *http.Request) {
|
||||
caps := models.DeviceCapabilities{
|
||||
Platform: true,
|
||||
CrossPlatform: true,
|
||||
Conditional: true,
|
||||
}
|
||||
views.CapabilitiesResult(caps).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleRegisterVerifyCode(w http.ResponseWriter, r *http.Request) {
|
||||
state := models.RegisterState{Step: 3}
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
views.RegisterStepWithStepper(state).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
views.RegisterPage(state).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
state := models.LoginState{Step: "1"}
|
||||
views.LoginPage(state).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleLoginStep(w http.ResponseWriter, r *http.Request) {
|
||||
step := r.PathValue("step")
|
||||
if step == "" {
|
||||
step = "1"
|
||||
}
|
||||
|
||||
state := models.LoginState{Step: step}
|
||||
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
views.LoginStepWithOOB(state).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
|
||||
views.LoginPage(state).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
var qrPollCount = 0
|
||||
|
||||
func handleLoginQRStatus(w http.ResponseWriter, r *http.Request) {
|
||||
qrPollCount++
|
||||
if qrPollCount >= 3 {
|
||||
qrPollCount = 0
|
||||
views.QRStatusSuccess().Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
views.QRStatusWaiting().Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleAuthorize(w http.ResponseWriter, r *http.Request) {
|
||||
reqType := r.URL.Query().Get("type")
|
||||
req := models.DefaultAuthRequest(reqType)
|
||||
views.AuthorizePage(req).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleAuthorizeApprove(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
actionType := r.FormValue("type")
|
||||
if actionType == "" {
|
||||
actionType = "connect"
|
||||
}
|
||||
views.AuthResultSuccess(actionType).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleAuthorizeDeny(w http.ResponseWriter, r *http.Request) {
|
||||
views.AuthResultDenied().Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
tab := r.URL.Query().Get("tab")
|
||||
if tab == "" {
|
||||
tab = "overview"
|
||||
}
|
||||
data := models.DefaultDashboardData()
|
||||
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
views.DashboardContent(data, tab).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
|
||||
views.DashboardPage(data, tab).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
tab := r.URL.Query().Get("tab")
|
||||
if tab == "" {
|
||||
tab = "profile"
|
||||
}
|
||||
data := models.DefaultSettingsData()
|
||||
views.SettingsPage(data, tab).Render(r.Context(), w)
|
||||
}
|
||||
@@ -1,36 +1,44 @@
|
||||
package layouts
|
||||
|
||||
// Base provides the HTML document structure for all pages
|
||||
import "nebula/pkg/config"
|
||||
|
||||
templ Base(title string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="wa-cloak wa-theme-dark">
|
||||
<html lang="en" class={ "wa-cloak", config.FromContext(ctx).ThemeClass() }>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="color-scheme" content="dark light"/>
|
||||
<title>{ title } - Sonr Motr Wallet</title>
|
||||
<meta name="htmx-config" content={ config.FromContext(ctx).HTMXConfigJSON() }/>
|
||||
<title>{ title } - { config.FromContext(ctx).AppTitle }</title>
|
||||
<script src="https://kit.webawesome.com/47c7425b971f443c.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/htmx.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/ext/hx-preload.js"></script>
|
||||
if config.FromContext(ctx).Preload {
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/ext/hx-preload.js"></script>
|
||||
}
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
||||
<style>
|
||||
:root {
|
||||
--wa-color-primary: #17c2ff;
|
||||
--sidebar-width: 64px;
|
||||
}
|
||||
html, body {
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
}
|
||||
wa-button, a, [onclick], [hx-get], [hx-post] {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@baseStyles()
|
||||
</head>
|
||||
<body>
|
||||
{ children... }
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ baseStyles() {
|
||||
<style>
|
||||
:root {
|
||||
--wa-color-primary: #17c2ff;
|
||||
--sidebar-width: 64px;
|
||||
}
|
||||
html, body {
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: default;
|
||||
}
|
||||
wa-button, a, [onclick], [hx-get], [hx-post] {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ package layouts
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
// Base provides the HTML document structure for all pages
|
||||
import "nebula"
|
||||
|
||||
func Base(title string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
@@ -30,20 +31,86 @@ func Base(title string) templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"wa-cloak wa-theme-dark\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"dark light\"><title>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 11, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
var templ_7745c5c3_Var2 = []any{"wa-cloak", nebula.ConfigFromContext(ctx).ThemeClass()}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Sonr Motr Wallet</title><script src=\"https://kit.webawesome.com/47c7425b971f443c.js\" crossorigin=\"anonymous\"></script><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/htmx.min.js\"></script><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/ext/hx-preload.js\"></script><script src=\"https://cdn.jsdelivr.net/npm/d3@7\"></script><style>\n\t\t\t\t:root {\n\t\t\t\t\t--wa-color-primary: #17c2ff;\n\t\t\t\t\t--sidebar-width: 64px;\n\t\t\t\t}\n\t\t\t\thtml, body {\n\t\t\t\t\tmin-height: 100%;\n\t\t\t\t\tpadding: 0;\n\t\t\t\t\tmargin: 0;\n\t\t\t\t\tcursor: default;\n\t\t\t\t}\n\t\t\t\twa-button, a, [onclick], [hx-get], [hx-post] {\n\t\t\t\t\tcursor: pointer;\n\t\t\t\t}\n\t\t\t</style></head><body>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<html lang=\"en\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"dark light\"><meta name=\"htmx-config\" content=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(nebula.ConfigFromContext(ctx).HTMXConfigJSON())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 12, Col: 84}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><title>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 13, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " - ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(nebula.ConfigFromContext(ctx).AppTitle)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 13, Col: 62}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</title><script src=\"https://kit.webawesome.com/47c7425b971f443c.js\" crossorigin=\"anonymous\"></script><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/htmx.min.js\"></script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if nebula.ConfigFromContext(ctx).Preload {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<script src=\"https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha5/dist/ext/hx-preload.js\"></script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<script src=\"https://cdn.jsdelivr.net/npm/d3@7\"></script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = baseStyles().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</head><body>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -51,7 +118,36 @@ func Base(title string) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func baseStyles() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var7 == nil {
|
||||
templ_7745c5c3_Var7 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<style>\n\t\t:root {\n\t\t\t--wa-color-primary: #17c2ff;\n\t\t\t--sidebar-width: 64px;\n\t\t}\n\t\thtml, body {\n\t\t\tmin-height: 100%;\n\t\t\tpadding: 0;\n\t\t\tmargin: 0;\n\t\t\tcursor: default;\n\t\t}\n\t\twa-button, a, [onclick], [hx-get], [hx-post] {\n\t\t\tcursor: pointer;\n\t\t}\n\t</style>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user