第三批(最终批):把 root 中剩下所有领域文件按功能搬到 internal/ 下。
完成后根目录从 46 个 .go 文件降到 14 个,其中 11 个是 bridge(type alias
+ thin wrapper),仅供尚未改造的 main.go 引用。
新增包
- internal/bot/ Service / TextSender / NewPKIKeyResolver /
RegisterRoutes,含 PKI 直连发送、节点信息广播、
outbound DM 持久化等业务逻辑。
- internal/sign/ SignDTO / SignDayCountDTO / RegisterAdminRoutes,
把原来分散在 admin_sign_routes.go 与 web.go 中的
sign DTO/路由收拢到一处。
- internal/mapsource/ AdminDTO / PublicDTO / RegisterAdminRoutes /
RegisterPublicRoutes。
- internal/llmadmin/ LLM 消息队列、Provider、ToolRouter、PrimaryConfig 的
admin 路由。
- internal/web/ 路由总入口(NewRouter/NewHTTPServer/ServeUnixSocket)、
各资源的 GET API、admin 用户/登录/MQTT 状态、所有
DTO 函数。把 auth.go 的 sessionClaims 升级为
auth.SessionClaims;mqtt_status.go 重写成
MQTTRuntimeStatus / AdminMQTTStatus 结构体并把
client info 解析在 web 包内自带,不再依赖 main 包。
map_tile_proxy_routes 与测试一起搬入。
修改
- web.go 中 parseListOptions / writeListResponse / ptrString 等本地 helper
改为对 internal/webutil 的 thin wrapper,避免重复实现。
- internal/auth 在 step 4 已创建,本批中 web 包正式开始引用其 Manager /
RequireAdmin / SessionClaims。
根目录新增 bridge:bot_bridge.go / sign_bridge.go / mapsource_bridge.go /
llmadmin_bridge.go / web_bridge.go。后者把 mqttRuntimeStatus 包成
webpkg.MQTTStatusProvider 适配器,使 main.go 中旧字段名保持可用。
go build ./... / go test ./... 全部通过;测试数量未变。
Co-Authored-By: Claude <noreply@anthropic.com>
133 lines
3.9 KiB
Go
133 lines
3.9 KiB
Go
// Package sign 提供签到记录的 admin 路由与对应的 DTO/列表查询。
|
|
//
|
|
// 拆离自原来 main 包的 admin_sign_routes.go 与 web.go 中的 signDTO /
|
|
// signDayCountDTO;其它 admin 路由也通过 SignDTO / SignDayCountDTO 复用。
|
|
package sign
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
storepkg "meshtastic_mqtt_server/internal/store"
|
|
"meshtastic_mqtt_server/internal/webutil"
|
|
)
|
|
|
|
type signRequest struct {
|
|
NodeID string `json:"node_id"`
|
|
LongName string `json:"long_name"`
|
|
ShortName string `json:"short_name"`
|
|
SignText string `json:"sign_text"`
|
|
SignTime string `json:"sign_time"`
|
|
}
|
|
|
|
// RegisterAdminRoutes 在 admin 路由组下挂 sign CRUD 端点。
|
|
func RegisterAdminRoutes(r gin.IRouter, store *storepkg.Store) {
|
|
r.GET("/signs", func(c *gin.Context) {
|
|
opts, ok := webutil.ParseListOptions(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
rows, err := store.ListSigns(opts)
|
|
if err != nil {
|
|
webutil.WriteListResponse(c, rows, opts, err, SignDTO)
|
|
return
|
|
}
|
|
total, err := store.CountSigns(opts)
|
|
webutil.WriteListResponseWithTotal(c, rows, opts, total, err, SignDTO)
|
|
})
|
|
r.POST("/signs", func(c *gin.Context) {
|
|
var req signRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid sign request"})
|
|
return
|
|
}
|
|
signTime, ok := parseSignRequestTime(c, req.SignTime)
|
|
if !ok {
|
|
return
|
|
}
|
|
row, err := store.CreateSign(req.NodeID, storepkg.NullableString(req.LongName), storepkg.NullableString(req.ShortName), req.SignText, signTime)
|
|
writeSignMutationResponse(c, http.StatusCreated, row, err)
|
|
})
|
|
r.PUT("/signs/:id", func(c *gin.Context) {
|
|
id, ok := parseSignID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req signRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid sign request"})
|
|
return
|
|
}
|
|
signTime, ok := parseSignRequestTime(c, req.SignTime)
|
|
if !ok {
|
|
return
|
|
}
|
|
row, err := store.UpdateSign(id, req.NodeID, storepkg.NullableString(req.LongName), storepkg.NullableString(req.ShortName), req.SignText, signTime)
|
|
writeSignMutationResponse(c, http.StatusOK, row, err)
|
|
})
|
|
r.DELETE("/signs/:id", func(c *gin.Context) {
|
|
id, ok := parseSignID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
err := store.DeleteSign(id)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "sign record not found"})
|
|
return
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
|
})
|
|
}
|
|
|
|
func parseSignID(c *gin.Context) (uint64, bool) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil || id == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid sign id"})
|
|
return 0, false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func parseSignRequestTime(c *gin.Context, value string) (time.Time, bool) {
|
|
if value == "" {
|
|
return time.Time{}, true
|
|
}
|
|
parsed, err := time.Parse(time.RFC3339, value)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid sign_time: use RFC3339"})
|
|
return time.Time{}, false
|
|
}
|
|
return parsed, true
|
|
}
|
|
|
|
func writeSignMutationResponse(c *gin.Context, status int, row *storepkg.SignRecord, err error) {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "sign record not found"})
|
|
return
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(status, gin.H{"item": SignDTO(*row)})
|
|
}
|
|
|
|
// SignDTO 把 SignRecord 转成给前端的视图。
|
|
func SignDTO(row storepkg.SignRecord) gin.H {
|
|
return gin.H{"id": row.ID, "node_id": row.NodeID, "long_name": webutil.PtrString(row.LongName), "short_name": webutil.PtrString(row.ShortName), "sign_text": row.SignText, "sign_time": row.SignTime}
|
|
}
|
|
|
|
// SignDayCountDTO 把按日聚合的签到数量转成视图。
|
|
func SignDayCountDTO(row storepkg.SignDayCount) gin.H {
|
|
return gin.H{"date": row.Date, "count": row.Count}
|
|
}
|