summaryrefslogtreecommitdiff
path: root/api/devices/devices.go
diff options
context:
space:
mode:
authorAlexander Hunt <me@liz.coffee>2026-06-21 12:43:31 -0700
committerAlexander Hunt <me@liz.coffee>2026-06-21 12:43:31 -0700
commitcfc0d34a584254a9ed76620951c810771aff6f3b (patch)
tree7fba034f7347ce0003e7a3a56adcaf862c834907 /api/devices/devices.go
parentfb653292612eddca5b088ed88466c546d1597107 (diff)
downloadpenguins.lan-cfc0d34a584254a9ed76620951c810771aff6f3b.tar.gz
penguins.lan-cfc0d34a584254a9ed76620951c810771aff6f3b.zip
STUFF
Diffstat (limited to 'api/devices/devices.go')
-rw-r--r--api/devices/devices.go92
1 files changed, 92 insertions, 0 deletions
diff --git a/api/devices/devices.go b/api/devices/devices.go
new file mode 100644
index 0000000..227afcb
--- /dev/null
+++ b/api/devices/devices.go
@@ -0,0 +1,92 @@
+package devices
+
+import (
+ "log"
+ "net/http"
+
+ "penguins.lan/api/types"
+ "penguins.lan/database"
+ "penguins.lan/utils"
+)
+
+// DeviceView is one device row for the profile page.
+type DeviceView struct {
+ Mac string // canonical colon form, used as the set-primary value
+ Name string // <compact-mac>.<username>.<zone> FQDN
+ IP string
+ Online bool
+ LastSeen string
+ Primary bool
+}
+
+// ListDevicesContinuation loads the user's devices (with liveness + which is
+// primary) and the zone facts the profile page and wizard need.
+func ListDevicesContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ if context.User == nil {
+ return failure(context, req, resp)
+ }
+
+ macs, err := database.ListUserMacs(context.DBConn, context.User.ID)
+ if err != nil {
+ log.Println("devices: list failed:", err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ primary, err := database.GetUserPrimaryMAC(context.DBConn, context.User.ID)
+ if err != nil {
+ log.Println("devices: primary lookup failed:", err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ zone := context.Args.DnsZone
+ userDomain := context.User.Username + "." + zone
+
+ views := make([]DeviceView, 0, len(macs))
+ for _, d := range macs {
+ name := d.Mac
+ if mac, err := utils.ParseMac(d.Mac); err == nil {
+ name = mac.Compact() + "." + userDomain
+ }
+ views = append(views, DeviceView{
+ Mac: d.Mac,
+ Name: name,
+ IP: d.IP,
+ Online: d.Online,
+ LastSeen: utils.FormatSince(d.LastSeen),
+ Primary: d.Mac == primary,
+ })
+ }
+
+ (*context.TemplateData)["Devices"] = views
+ (*context.TemplateData)["PrimaryMAC"] = primary
+ (*context.TemplateData)["DnsZone"] = zone
+ (*context.TemplateData)["UserDomain"] = userDomain
+ return success(context, req, resp)
+ }
+}
+
+// SetPrimaryContinuation marks one of the caller's devices as primary.
+func SetPrimaryContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ mac := req.FormValue("mac")
+ owned, err := database.MACBelongsToUser(context.DBConn, mac, context.User.ID)
+ if err != nil {
+ log.Println("devices: ownership check failed:", err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ if !owned {
+ resp.WriteHeader(http.StatusUnauthorized)
+ return failure(context, req, resp)
+ }
+ if err := database.SetUserPrimaryMAC(context.DBConn, context.User.ID, mac); err != nil {
+ log.Println("devices: set primary failed:", err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ http.Redirect(resp, req, "/profile", http.StatusSeeOther)
+ return success(context, req, resp)
+ }
+}