156 lines
4.3 KiB
Go
156 lines
4.3 KiB
Go
package store
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"meshtastic_mqtt_server/internal/config"
|
|
)
|
|
|
|
func (s *Store) ListSigns(opts ListOptions) ([]SignRecord, error) {
|
|
opts = NormalizeListOptions(opts)
|
|
var rows []SignRecord
|
|
q := applySignFilters(s.db.Model(&SignRecord{}), opts).
|
|
Order("sign_time DESC").
|
|
Order("id DESC").
|
|
Limit(opts.Limit).
|
|
Offset(opts.Offset)
|
|
return rows, q.Find(&rows).Error
|
|
}
|
|
|
|
type SignDayCount struct {
|
|
Date string `gorm:"column:sign_date"`
|
|
Count int64 `gorm:"column:count"`
|
|
}
|
|
|
|
func (s *Store) CountSigns(opts ListOptions) (int64, error) {
|
|
var total int64
|
|
q := applySignFilters(s.db.Model(&SignRecord{}), opts)
|
|
return total, q.Count(&total).Error
|
|
}
|
|
|
|
func (s *Store) CountSignsByDay(opts ListOptions) ([]SignDayCount, error) {
|
|
var rows []SignDayCount
|
|
dateExpr := "strftime('%Y-%m-%d', sign_time)"
|
|
if s.driver == config.DriverMySQL {
|
|
dateExpr = "DATE_FORMAT(sign_time, '%Y-%m-%d')"
|
|
}
|
|
q := applySignFilters(s.db.Model(&SignRecord{}), opts).
|
|
Select(dateExpr + " AS sign_date, COUNT(*) AS count").
|
|
Group(dateExpr).
|
|
Order("sign_date DESC")
|
|
return rows, q.Scan(&rows).Error
|
|
}
|
|
|
|
// HasSignedOnDay 判断指定节点在 day 所属的自然日(按 day 的时区)是否已有签到记录。
|
|
// 用 Go 端计算当日起止时间再查询,避免依赖 SQLite/MySQL 各自的日期函数。
|
|
func (s *Store) HasSignedOnDay(nodeID string, day time.Time) (bool, error) {
|
|
nodeID = strings.TrimSpace(nodeID)
|
|
if nodeID == "" {
|
|
return false, fmt.Errorf("node id is required")
|
|
}
|
|
loc := day.Location()
|
|
if loc == nil {
|
|
loc = time.Local
|
|
}
|
|
start := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, loc)
|
|
end := start.AddDate(0, 0, 1)
|
|
var count int64
|
|
if err := s.db.Model(&SignRecord{}).
|
|
Where("node_id = ? AND sign_time >= ? AND sign_time < ?", nodeID, start, end).
|
|
Count(&count).Error; err != nil {
|
|
return false, fmt.Errorf("check sign on day: %w", err)
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
func (s *Store) GetSignByID(id uint64) (*SignRecord, error) {
|
|
var row SignRecord
|
|
if err := s.db.Where("id = ?", id).Take(&row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &row, nil
|
|
}
|
|
|
|
func (s *Store) CreateSign(nodeID string, longName, shortName *string, signText string, signTime time.Time) (*SignRecord, error) {
|
|
nodeID = strings.TrimSpace(nodeID)
|
|
signText = strings.TrimSpace(signText)
|
|
if nodeID == "" {
|
|
return nil, fmt.Errorf("node id is required")
|
|
}
|
|
if signText == "" {
|
|
return nil, fmt.Errorf("sign text is required")
|
|
}
|
|
if signTime.IsZero() {
|
|
signTime = time.Now()
|
|
}
|
|
row := SignRecord{NodeID: nodeID, LongName: trimNullableString(longName), ShortName: trimNullableString(shortName), SignText: signText, SignTime: signTime}
|
|
if err := s.db.Create(&row).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &row, nil
|
|
}
|
|
|
|
func (s *Store) UpdateSign(id uint64, nodeID string, longName, shortName *string, signText string, signTime time.Time) (*SignRecord, error) {
|
|
if id == 0 {
|
|
return nil, fmt.Errorf("sign id is required")
|
|
}
|
|
nodeID = strings.TrimSpace(nodeID)
|
|
signText = strings.TrimSpace(signText)
|
|
if nodeID == "" {
|
|
return nil, fmt.Errorf("node id is required")
|
|
}
|
|
if signText == "" {
|
|
return nil, fmt.Errorf("sign text is required")
|
|
}
|
|
if signTime.IsZero() {
|
|
signTime = time.Now()
|
|
}
|
|
if _, err := s.GetSignByID(id); err != nil {
|
|
return nil, err
|
|
}
|
|
updates := map[string]any{"node_id": nodeID, "long_name": trimNullableString(longName), "short_name": trimNullableString(shortName), "sign_text": signText, "sign_time": signTime}
|
|
if err := s.db.Model(&SignRecord{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return s.GetSignByID(id)
|
|
}
|
|
|
|
func (s *Store) DeleteSign(id uint64) error {
|
|
result := s.db.Where("id = ?", id).Delete(&SignRecord{})
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func applySignFilters(q *gorm.DB, opts ListOptions) *gorm.DB {
|
|
if opts.NodeID != "" {
|
|
q = q.Where("node_id = ?", opts.NodeID)
|
|
}
|
|
if opts.Since != nil {
|
|
q = q.Where("sign_time >= ?", *opts.Since)
|
|
}
|
|
if opts.Until != nil {
|
|
q = q.Where("sign_time <= ?", *opts.Until)
|
|
}
|
|
return q
|
|
}
|
|
|
|
func trimNullableString(value *string) *string {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
trimmed := strings.TrimSpace(*value)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
return &trimmed
|
|
}
|