diff --git a/server/api/app/app.go b/server/api/app/app.go new file mode 100644 index 0000000..d0ae7f7 --- /dev/null +++ b/server/api/app/app.go @@ -0,0 +1,16 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +package app + +import ( + "context" + + "stackChan/api/app/v1" +) + +type IAppV1 interface { + GetAppList(ctx context.Context, req *v1.GetAppListReq) (res *v1.GetAppListRes, err error) +} diff --git a/server/api/app/v1/app.go b/server/api/app/v1/app.go new file mode 100644 index 0000000..8d9aa04 --- /dev/null +++ b/server/api/app/v1/app.go @@ -0,0 +1,23 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type GetAppListReq struct { + g.Meta `path:"/apps" method:"get" tags:"App" summary:"List installable apps offered by the App Center"` +} + +type AppInfo struct { + AppName string `json:"appName" dc:"App display name"` + IconUrl string `json:"iconUrl" dc:"Icon URL"` + Description string `json:"description" dc:"Short description"` + FirmwareUrl string `json:"firmwareUrl" dc:"OTA firmware URL for installation"` +} + +type GetAppListRes []AppInfo diff --git a/server/api/device/device.go b/server/api/device/device.go index edf8d0f..5373380 100644 --- a/server/api/device/device.go +++ b/server/api/device/device.go @@ -16,4 +16,6 @@ type IDeviceV1 interface { GetRandomDevice(ctx context.Context, req *v1.GetRandomDeviceReq) (res *v1.GetRandomDeviceRes, err error) GetDeviceInfo(ctx context.Context, req *v1.GetDeviceInfoReq) (res *v1.GetDeviceInfoRes, err error) UpdateDeviceInfo(ctx context.Context, req *v1.UpdateDeviceInfoReq) (res *v1.UpdateDeviceInfoRes, err error) + GetUserAccountInfo(ctx context.Context, req *v1.GetUserAccountInfoReq) (res *v1.GetUserAccountInfoRes, err error) + UnbindAccount(ctx context.Context, req *v1.UnbindAccountReq) (res *v1.UnbindAccountRes, err error) } diff --git a/server/api/device/v1/device.go b/server/api/device/v1/device.go index d8f338b..6369b52 100644 --- a/server/api/device/v1/device.go +++ b/server/api/device/v1/device.go @@ -39,7 +39,7 @@ type GetRandomDeviceRes []entity.Device type GetDeviceInfoReq struct { g.Meta `path:"/device/info" method:"get" tags:"Device" summary:"Device Info Get request"` - Mac string `json:"mac" v:"required" description:"Mac address"` + Mac string `json:"mac" description:"Mac address. Optional: when empty, returns an empty device info placeholder so authenticated firmware calls that do not carry a mac still receive a well-formed response."` } type GetDeviceInfoRes model.DeviceInfo @@ -51,3 +51,17 @@ type UpdateDeviceInfoReq struct { } type UpdateDeviceInfoRes string + +type GetUserAccountInfoReq struct { + g.Meta `path:"/device/user" method:"get" tags:"Device" summary:"Get the user account bound to the authenticated device"` +} + +type GetUserAccountInfoRes struct { + Username string `json:"username" dc:"Username bound to the device"` +} + +type UnbindAccountReq struct { + g.Meta `path:"/device/unbind" method:"post" tags:"Device" summary:"Unbind the authenticated device from its user account"` +} + +type UnbindAccountRes struct{} diff --git a/server/internal/cmd/cmd.go b/server/internal/cmd/cmd.go index ca75193..24d8820 100644 --- a/server/internal/cmd/cmd.go +++ b/server/internal/cmd/cmd.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "path/filepath" + "stackChan/internal/controller/app" "stackChan/internal/controller/dance" "stackChan/internal/controller/device" "stackChan/internal/controller/file" @@ -67,7 +68,7 @@ var ( s.Group("/stackChan", func(group *ghttp.RouterGroup) { group.Middleware(ghttp.MiddlewareHandlerResponse) - group.Bind(device.NewV1(), friend.NewV1(), dance.NewV1(), file.NewV1(), post.NewV1()) + group.Bind(device.NewV1(), friend.NewV1(), dance.NewV1(), file.NewV1(), post.NewV1(), app.NewV1()) }) s.Run() return nil diff --git a/server/internal/controller/app/app.go b/server/internal/controller/app/app.go new file mode 100644 index 0000000..d920ab0 --- /dev/null +++ b/server/internal/controller/app/app.go @@ -0,0 +1,10 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package app diff --git a/server/internal/controller/app/app_new.go b/server/internal/controller/app/app_new.go new file mode 100644 index 0000000..433d8d3 --- /dev/null +++ b/server/internal/controller/app/app_new.go @@ -0,0 +1,20 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package app + +import ( + "stackChan/api/app" +) + +type ControllerV1 struct{} + +func NewV1() app.IAppV1 { + return &ControllerV1{} +} diff --git a/server/internal/controller/app/app_v1_get_app_list.go b/server/internal/controller/app/app_v1_get_app_list.go new file mode 100644 index 0000000..827e8c9 --- /dev/null +++ b/server/internal/controller/app/app_v1_get_app_list.go @@ -0,0 +1,24 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +package app + +import ( + "context" + + "stackChan/api/app/v1" +) + +// GetAppList returns the list of apps offered by the App Center. The +// firmware consumes this list in firmware/main/hal/hal_app_center.cpp to +// populate the Mooncake AppCenter UI and to trigger OTA downloads. +// +// This minimal implementation returns an empty list so self-hosted +// deployments start with a clean slate. Production deployments are +// expected to replace the body with a DB-backed catalogue. +func (c *ControllerV1) GetAppList(ctx context.Context, req *v1.GetAppListReq) (res *v1.GetAppListRes, err error) { + empty := v1.GetAppListRes{} + return &empty, nil +} diff --git a/server/internal/controller/device/device_v1_get_device_info.go b/server/internal/controller/device/device_v1_get_device_info.go index 6a317ea..60a86d5 100644 --- a/server/internal/controller/device/device_v1_get_device_info.go +++ b/server/internal/controller/device/device_v1_get_device_info.go @@ -14,6 +14,9 @@ import ( ) func (c *ControllerV1) GetDeviceInfo(ctx context.Context, req *v1.GetDeviceInfoReq) (res *v1.GetDeviceInfoRes, err error) { + if req.Mac == "" { + return (*v1.GetDeviceInfoRes)(&model.DeviceInfo{}), nil + } var info model.DeviceInfo err = dao.Device.Ctx(ctx).WherePri(req.Mac).Scan(&info) if err != nil { diff --git a/server/internal/controller/device/device_v1_get_user_account_info.go b/server/internal/controller/device/device_v1_get_user_account_info.go new file mode 100644 index 0000000..803f77e --- /dev/null +++ b/server/internal/controller/device/device_v1_get_user_account_info.go @@ -0,0 +1,24 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +package device + +import ( + "context" + + "stackChan/api/device/v1" +) + +// GetUserAccountInfo returns the username bound to the authenticated device. +// The firmware calls this endpoint right after boot to sync the account +// label displayed in the setup app (see firmware/main/hal/hal_account.cpp). +// +// This minimal implementation returns a default placeholder so that +// self-hosted deployments work out of the box. Production deployments are +// expected to extend this handler with a real account lookup based on the +// Authorization header token. +func (c *ControllerV1) GetUserAccountInfo(ctx context.Context, req *v1.GetUserAccountInfoReq) (res *v1.GetUserAccountInfoRes, err error) { + return &v1.GetUserAccountInfoRes{Username: "Self-hosted User"}, nil +} diff --git a/server/internal/controller/device/device_v1_unbind_account.go b/server/internal/controller/device/device_v1_unbind_account.go new file mode 100644 index 0000000..21afd42 --- /dev/null +++ b/server/internal/controller/device/device_v1_unbind_account.go @@ -0,0 +1,23 @@ +/* +SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD +SPDX-License-Identifier: MIT +*/ + +package device + +import ( + "context" + + "stackChan/api/device/v1" +) + +// UnbindAccount removes the binding between the authenticated device and +// its user account. The firmware calls this endpoint when the user triggers +// "Unbind" from the setup app (see firmware/main/hal/hal_account.cpp). +// +// This minimal implementation is a no-op so the firmware flow completes +// successfully. Production deployments are expected to delete the real +// account binding identified by the Authorization header token. +func (c *ControllerV1) UnbindAccount(ctx context.Context, req *v1.UnbindAccountReq) (res *v1.UnbindAccountRes, err error) { + return &v1.UnbindAccountRes{}, nil +}