add htmx web frontend with templates and session auth
This commit is contained in:
194
portal/internal/web/handlers.go
Normal file
194
portal/internal/web/handlers.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/omixlab/mosis-portal/internal/database"
|
||||
)
|
||||
|
||||
// Handler handles web page requests
|
||||
type Handler struct {
|
||||
db *database.DB
|
||||
templates *Templates
|
||||
}
|
||||
|
||||
// NewHandler creates a new web handler
|
||||
func NewHandler(db *database.DB) (*Handler, error) {
|
||||
templates, err := NewTemplates()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Handler{
|
||||
db: db,
|
||||
templates: templates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PageData is the base data structure for all pages
|
||||
type PageData struct {
|
||||
Title string
|
||||
ActiveNav string
|
||||
Developer *database.Developer
|
||||
Error string
|
||||
}
|
||||
|
||||
// Login renders the login page
|
||||
func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
data := PageData{
|
||||
Title: "Sign In",
|
||||
}
|
||||
|
||||
// Check for error message
|
||||
if err := r.URL.Query().Get("error"); err != "" {
|
||||
data.Error = err
|
||||
}
|
||||
|
||||
h.render(w, "login", data)
|
||||
}
|
||||
|
||||
// Dashboard renders the dashboard page
|
||||
func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
developer := getDeveloperFromContext(r)
|
||||
if developer == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Get stats
|
||||
apps, total, _ := h.db.ListApps(r.Context(), developer.ID, "", 1, 100)
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
Stats struct {
|
||||
TotalApps int
|
||||
Downloads int64
|
||||
ActiveUsers int64
|
||||
}
|
||||
Apps []*database.App
|
||||
}{
|
||||
PageData: PageData{
|
||||
Title: "Dashboard",
|
||||
ActiveNav: "dashboard",
|
||||
Developer: developer,
|
||||
},
|
||||
Apps: apps,
|
||||
}
|
||||
data.Stats.TotalApps = total
|
||||
data.Stats.Downloads = 0 // TODO: implement
|
||||
data.Stats.ActiveUsers = 0 // TODO: implement
|
||||
|
||||
h.render(w, "dashboard", data)
|
||||
}
|
||||
|
||||
// AppNew renders the new app form
|
||||
func (h *Handler) AppNew(w http.ResponseWriter, r *http.Request) {
|
||||
developer := getDeveloperFromContext(r)
|
||||
if developer == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
data := PageData{
|
||||
Title: "Create New App",
|
||||
ActiveNav: "apps",
|
||||
Developer: developer,
|
||||
}
|
||||
|
||||
h.render(w, "app_new", data)
|
||||
}
|
||||
|
||||
// AppDetail renders the app detail page
|
||||
func (h *Handler) AppDetail(w http.ResponseWriter, r *http.Request) {
|
||||
developer := getDeveloperFromContext(r)
|
||||
if developer == nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
appID := chi.URLParam(r, "appID")
|
||||
app, err := h.db.GetApp(r.Context(), appID)
|
||||
if err != nil {
|
||||
http.Error(w, "App not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
if app.DeveloperID != developer.ID {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Get latest version
|
||||
versions, totalVersions, _ := h.db.ListVersions(r.Context(), appID, "", 1, 1)
|
||||
var latestVersion *database.AppVersion
|
||||
if len(versions) > 0 {
|
||||
latestVersion = versions[0]
|
||||
}
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
App *database.App
|
||||
Tab string
|
||||
LatestVersion *database.AppVersion
|
||||
TotalVersions int
|
||||
}{
|
||||
PageData: PageData{
|
||||
Title: app.Name,
|
||||
ActiveNav: "apps",
|
||||
Developer: developer,
|
||||
},
|
||||
App: app,
|
||||
Tab: "overview",
|
||||
LatestVersion: latestVersion,
|
||||
TotalVersions: totalVersions,
|
||||
}
|
||||
|
||||
h.render(w, "app_detail", data)
|
||||
}
|
||||
|
||||
// AppListPartial renders the app list partial for htmx
|
||||
func (h *Handler) AppListPartial(w http.ResponseWriter, r *http.Request) {
|
||||
developer := getDeveloperFromContext(r)
|
||||
if developer == nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
apps, _, err := h.db.ListApps(r.Context(), developer.ID, "", 1, 100)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to load apps", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Apps []*database.App
|
||||
}{
|
||||
Apps: apps,
|
||||
}
|
||||
|
||||
h.renderPartial(w, "app_list", data)
|
||||
}
|
||||
|
||||
// render renders a full page template
|
||||
func (h *Handler) render(w http.ResponseWriter, name string, data interface{}) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := h.templates.RenderPage(w, name, data); err != nil {
|
||||
http.Error(w, "Template error: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// renderPartial renders a partial template
|
||||
func (h *Handler) renderPartial(w http.ResponseWriter, name string, data interface{}) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := h.templates.RenderPartial(w, name, data); err != nil {
|
||||
http.Error(w, "Template error: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// getDeveloperFromContext retrieves the developer from the request context
|
||||
// This will be set by the session middleware
|
||||
func getDeveloperFromContext(r *http.Request) *database.Developer {
|
||||
developer, _ := r.Context().Value("developer").(*database.Developer)
|
||||
return developer
|
||||
}
|
||||
Reference in New Issue
Block a user