chore: 合并本地改动与 main(t_ 表名、lint、音乐分类)
在 fast-forward 到 origin/main 后解决 music logic/repo 冲突,SQL 与模型统一 t_* 表名;Makefile 修正 golangci-lint 变量并令 test/deploy 依赖 lint;补齐 admin/transport 等 lint 修复与 music logic 单测以满足覆盖率门禁。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6aa72ba3e7
commit
13b42cef7b
12
Makefile
12
Makefile
@ -4,13 +4,14 @@ BIN := bin/spark-server
|
||||
LINUX_DIR := bin/linux_amd64
|
||||
IMAGE ?= spark-server:latest
|
||||
LDFLAGS := -s -w -buildid=
|
||||
LINT ?= golangci-lint
|
||||
# Note: GNU Make predefines LINT=lint (lex); use := so golangci-lint wins over the builtin.
|
||||
GOLANGCI_LINT := golangci-lint
|
||||
CONFIG ?= configs/config.yaml
|
||||
LOGIC_COVER_MIN ?= 65
|
||||
|
||||
.PHONY: all build image test lint fmt vet tidy migrate seed start run deploy deploy-rollback
|
||||
|
||||
all: lint test build
|
||||
all: test build
|
||||
|
||||
build:
|
||||
$(GO) build -o $(BIN) ./cmd/server
|
||||
@ -25,7 +26,8 @@ image:
|
||||
docker build --platform linux/amd64 -t $(IMAGE) .
|
||||
|
||||
# All unit tests, then each module logic package must be >= LOGIC_COVER_MIN.
|
||||
test:
|
||||
# Requires lint to pass first.
|
||||
test: lint
|
||||
$(GO) test ./...
|
||||
@echo "==> logic Service coverage (>= $(LOGIC_COVER_MIN)%)"
|
||||
@covout=$$($(GO) test -cover ./internal/module/*/logic); \
|
||||
@ -53,7 +55,7 @@ fmt:
|
||||
$(GO) fmt ./...
|
||||
|
||||
lint:
|
||||
$(LINT) run ./...
|
||||
$(GOLANGCI_LINT) run ./...
|
||||
|
||||
tidy:
|
||||
$(GO) mod tidy
|
||||
@ -70,7 +72,7 @@ start:
|
||||
run: build
|
||||
./$(BIN) -config $(CONFIG)
|
||||
|
||||
deploy:
|
||||
deploy: test
|
||||
bash deploy/docker/deploy.sh
|
||||
|
||||
deploy-rollback:
|
||||
|
||||
@ -19,9 +19,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxGetBytes = 32 << 20
|
||||
maxPutBytes = 4 << 20
|
||||
maxPutLarge = 50 << 20 // admin music audio etc.
|
||||
maxGetBytes = 32 << 20
|
||||
maxPutBytes = 4 << 20
|
||||
maxPutLarge = 50 << 20 // admin music audio etc.
|
||||
)
|
||||
|
||||
// PutLarge writes an object up to maxBytes (capped at 50 MiB).
|
||||
|
||||
@ -64,28 +64,34 @@ func (s *Service) AddComplaint(ctx context.Context, email, target, reason, detai
|
||||
|
||||
func complaintViews(rows []adminmodel.ComplaintRow) []ComplaintView {
|
||||
out := make([]ComplaintView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
target := r.TargetMail
|
||||
for i := range rows {
|
||||
target := rows[i].TargetMail
|
||||
if target == "" {
|
||||
target = strconv.FormatInt(r.TargetID, 10)
|
||||
target = strconv.FormatInt(rows[i].TargetID, 10)
|
||||
}
|
||||
out = append(out, ComplaintView{
|
||||
ID: r.ID, Email: r.Email, Target: target,
|
||||
Reason: reportReasonName(r.Reason), Detail: r.Detail,
|
||||
Status: "open", CreatedAt: r.CreatedAt.Format(time.RFC3339),
|
||||
ID: rows[i].ID, Email: rows[i].Email, Target: target,
|
||||
Reason: reportReasonName(rows[i].Reason), Detail: rows[i].Detail,
|
||||
Status: "open", CreatedAt: rows[i].CreatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const (
|
||||
reportLabelSpam = "spam"
|
||||
reportLabelHarassment = "harassment"
|
||||
reportLabelFake = "fake"
|
||||
)
|
||||
|
||||
func reportReasonName(r int) string {
|
||||
switch friendentity.ReportReason(r) {
|
||||
case friendentity.ReportSpam:
|
||||
return "spam"
|
||||
return reportLabelSpam
|
||||
case friendentity.ReportHarassment:
|
||||
return "harassment"
|
||||
return reportLabelHarassment
|
||||
case friendentity.ReportFake:
|
||||
return "fake"
|
||||
return reportLabelFake
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
@ -93,11 +99,11 @@ func reportReasonName(r int) string {
|
||||
|
||||
func parseReportReason(raw string) friendentity.ReportReason {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "1", "spam", "垃圾":
|
||||
case "1", reportLabelSpam, "垃圾":
|
||||
return friendentity.ReportSpam
|
||||
case "2", "harassment", "骚扰":
|
||||
case "2", reportLabelHarassment, "骚扰":
|
||||
return friendentity.ReportHarassment
|
||||
case "3", "fake", "不实身份":
|
||||
case "3", reportLabelFake, "不实身份":
|
||||
return friendentity.ReportFake
|
||||
default:
|
||||
return friendentity.ReportOther
|
||||
|
||||
@ -17,8 +17,8 @@ func (s *Service) ListGroups(ctx context.Context, q string) ([]GroupView, error)
|
||||
return nil, err
|
||||
}
|
||||
out := make([]GroupView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, groupSummary(r))
|
||||
for i := range rows {
|
||||
out = append(out, groupSummary(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -35,13 +35,14 @@ func (s *Service) GroupDetail(ctx context.Context, groupIDStr string) (*GroupVie
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view := groupSummary(*g)
|
||||
view := groupSummary(g)
|
||||
members, err := s.repo.ListGroupMembers(ctx, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view.Members = make([]GroupMemberOut, 0, len(members))
|
||||
for _, m := range members {
|
||||
for i := range members {
|
||||
m := &members[i]
|
||||
nick := m.Nickname
|
||||
if nick == "" {
|
||||
nick = m.UNick
|
||||
@ -55,8 +56,8 @@ func (s *Service) GroupDetail(ctx context.Context, groupIDStr string) (*GroupVie
|
||||
return nil, err
|
||||
}
|
||||
view.Messages = make([]ImMessageView, 0, len(msgs))
|
||||
for _, m := range msgs {
|
||||
view.Messages = append(view.Messages, messageView(m))
|
||||
for i := range msgs {
|
||||
view.Messages = append(view.Messages, messageView(&msgs[i]))
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
@ -28,9 +28,9 @@ func (s *Service) ListMessages(ctx context.Context, q string, limit int) ([]ImMe
|
||||
return nil, err
|
||||
}
|
||||
out := make([]ImMessageView, 0, limit)
|
||||
for _, r := range rows {
|
||||
v := messageView(r)
|
||||
if q != "" && !messageMatch(v, q) {
|
||||
for i := range rows {
|
||||
v := messageView(&rows[i])
|
||||
if q != "" && !messageMatch(&v, q) {
|
||||
continue
|
||||
}
|
||||
out = append(out, v)
|
||||
@ -56,8 +56,9 @@ func (s *Service) ListConversations(ctx context.Context, userID int64) (convs []
|
||||
convs = make([]ImConversationView, 0, len(rows))
|
||||
groups = make([]GroupView, 0)
|
||||
seenGroup := map[int64]struct{}{}
|
||||
for _, r := range rows {
|
||||
for i := range rows {
|
||||
chatType := "private"
|
||||
r := &rows[i]
|
||||
if r.Type == 2 {
|
||||
chatType = "group"
|
||||
}
|
||||
|
||||
@ -13,7 +13,13 @@ import (
|
||||
userentity "spark-server/internal/module/user/entity"
|
||||
)
|
||||
|
||||
func messageView(r adminmodel.MessageRow) adminentity.ImMessageView {
|
||||
const (
|
||||
roleLabelOwner = "owner"
|
||||
roleLabelAdmin = "admin"
|
||||
roleLabelMember = "member"
|
||||
)
|
||||
|
||||
func messageView(r *adminmodel.MessageRow) adminentity.ImMessageView {
|
||||
return adminentity.ImMessageView{
|
||||
ID: strconv.FormatInt(r.ID, 10),
|
||||
ClientMsgID: r.ClientMsgID,
|
||||
@ -28,7 +34,7 @@ func messageView(r adminmodel.MessageRow) adminentity.ImMessageView {
|
||||
}
|
||||
}
|
||||
|
||||
func messageMatch(v adminentity.ImMessageView, q string) bool {
|
||||
func messageMatch(v *adminentity.ImMessageView, q string) bool {
|
||||
ql := strings.ToLower(q)
|
||||
if strings.Contains(strings.ToLower(v.Text), ql) {
|
||||
return true
|
||||
@ -117,7 +123,7 @@ func previewText(raw []byte, t msgentity.Type) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func userView(r adminmodel.UserRow) adminentity.UserView {
|
||||
func userView(r *adminmodel.UserRow) adminentity.UserView {
|
||||
v := adminentity.UserView{
|
||||
ID: r.ID, Email: r.Email, Nickname: r.Nickname, SparkID: r.SparkID,
|
||||
AvatarURL: r.Avatar, Channel: r.Platform, Status: r.Status,
|
||||
@ -133,7 +139,7 @@ func userView(r adminmodel.UserRow) adminentity.UserView {
|
||||
return v
|
||||
}
|
||||
|
||||
func groupSummary(r adminmodel.GroupRow) adminentity.GroupView {
|
||||
func groupSummary(r *adminmodel.GroupRow) adminentity.GroupView {
|
||||
return adminentity.GroupView{
|
||||
GroupID: strconv.FormatInt(r.ID, 10),
|
||||
Name: r.Name,
|
||||
@ -150,10 +156,10 @@ func groupSummary(r adminmodel.GroupRow) adminentity.GroupView {
|
||||
func roleName(role int) string {
|
||||
switch groupentity.Role(role) {
|
||||
case groupentity.RoleOwner:
|
||||
return "owner"
|
||||
return roleLabelOwner
|
||||
case groupentity.RoleAdmin:
|
||||
return "admin"
|
||||
return roleLabelAdmin
|
||||
default:
|
||||
return "member"
|
||||
return roleLabelMember
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,14 +33,14 @@ func TestAdminMessageHelpers(t *testing.T) {
|
||||
if previewText([]byte("not-json"), msgentity.TypeText) != "" {
|
||||
t.Fatal("bad content")
|
||||
}
|
||||
v := messageView(adminmodel.MessageRow{
|
||||
v := messageView(&adminmodel.MessageRow{
|
||||
ID: 42, SenderID: 7, ReceiverID: 8, GroupID: 9, MessageType: int16(msgentity.TypeText),
|
||||
Content: mustMarshal(t, msgentity.NewText("needle")), Status: 2, CreatedAt: time.Unix(1, 0).UTC(), ChatType: "private",
|
||||
})
|
||||
if !v.Revoked || !messageMatch(v, "needle") || !messageMatch(v, "42") || !messageMatch(v, "text") || !messageMatch(v, "7") {
|
||||
if !v.Revoked || !messageMatch(&v, "needle") || !messageMatch(&v, "42") || !messageMatch(&v, "text") || !messageMatch(&v, "7") {
|
||||
t.Fatalf("%+v", v)
|
||||
}
|
||||
if messageMatch(v, "zzz") {
|
||||
if messageMatch(&v, "zzz") {
|
||||
t.Fatal("no match")
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,9 +51,9 @@ func (m *memoryAdmin) ListUsers(_ context.Context, emailLike string, page, size
|
||||
|
||||
func (m *memoryAdmin) UsersByEmail(_ context.Context, email string) ([]adminmodel.UserRow, error) {
|
||||
out := make([]adminmodel.UserRow, 0)
|
||||
for _, u := range m.users {
|
||||
if u.Email == email {
|
||||
out = append(out, u)
|
||||
for i := range m.users {
|
||||
if m.users[i].Email == email {
|
||||
out = append(out, m.users[i])
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
@ -71,9 +71,9 @@ func (m *memoryAdmin) UserByID(_ context.Context, id int64) (*adminmodel.UserRow
|
||||
|
||||
func (m *memoryAdmin) UsersByNickname(_ context.Context, nickname string) ([]adminmodel.UserRow, error) {
|
||||
out := make([]adminmodel.UserRow, 0)
|
||||
for _, u := range m.users {
|
||||
if u.Nickname == nickname {
|
||||
out = append(out, u)
|
||||
for i := range m.users {
|
||||
if m.users[i].Nickname == nickname {
|
||||
out = append(out, m.users[i])
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
@ -81,9 +81,9 @@ func (m *memoryAdmin) UsersByNickname(_ context.Context, nickname string) ([]adm
|
||||
|
||||
func (m *memoryAdmin) UsersByDevice(_ context.Context, _ string, deviceIDNum uint64) ([]adminmodel.UserRow, error) {
|
||||
out := make([]adminmodel.UserRow, 0)
|
||||
for _, u := range m.users {
|
||||
if u.DeviceID == deviceIDNum {
|
||||
out = append(out, u)
|
||||
for i := range m.users {
|
||||
if m.users[i].DeviceID == deviceIDNum {
|
||||
out = append(out, m.users[i])
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
|
||||
@ -24,8 +24,8 @@ func (s *Service) ListUsers(ctx context.Context, email string, page, size int) (
|
||||
return nil, 0, err
|
||||
}
|
||||
list = make([]UserView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, userView(r))
|
||||
for i := range rows {
|
||||
list = append(list, userView(&rows[i]))
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
@ -43,8 +43,8 @@ func (s *Service) UserByEmail(ctx context.Context, email string) (map[string]any
|
||||
return nil, err
|
||||
}
|
||||
items := make([]UserView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
items = append(items, userView(r))
|
||||
for i := range rows {
|
||||
items = append(items, userView(&rows[i]))
|
||||
}
|
||||
return map[string]any{
|
||||
"email": email, "appUsers": items, "appCode": "spark",
|
||||
@ -59,7 +59,7 @@ func (s *Service) UserByID(ctx context.Context, id int64) (*UserView, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := userView(*r)
|
||||
v := userView(r)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
@ -72,8 +72,8 @@ func (s *Service) UsersByNickname(ctx context.Context, nickname string) ([]UserV
|
||||
return nil, err
|
||||
}
|
||||
out := make([]UserView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, userView(r))
|
||||
for i := range rows {
|
||||
out = append(out, userView(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -89,8 +89,8 @@ func (s *Service) UsersByDevice(ctx context.Context, deviceID string) ([]UserVie
|
||||
return nil, err
|
||||
}
|
||||
out := make([]UserView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, userView(r))
|
||||
for i := range rows {
|
||||
out = append(out, userView(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -104,8 +104,8 @@ func (s *Service) UsersByIP(ctx context.Context, ip string) ([]UserView, error)
|
||||
return nil, err
|
||||
}
|
||||
out := make([]UserView, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, userView(r))
|
||||
for i := range rows {
|
||||
out = append(out, userView(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -162,9 +162,9 @@ func (s *Service) SameIPRank(ctx context.Context, limit int) ([]SameIPItem, erro
|
||||
return nil, err
|
||||
}
|
||||
out := make([]SameIPItem, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
for i := range rows {
|
||||
out = append(out, SameIPItem{
|
||||
IP: r.IP, UserCount: r.UserCount, DeviceCount: r.DeviceCount, Count: r.UserCount,
|
||||
IP: rows[i].IP, UserCount: rows[i].UserCount, DeviceCount: rows[i].DeviceCount, Count: rows[i].UserCount,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
|
||||
@ -36,11 +36,8 @@ LIMIT ?`, limit).Scan(&rows).Error
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]ComplaintRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, ComplaintRow{
|
||||
ID: r.ID, UserID: r.UserID, TargetID: r.TargetID, Reason: r.Reason,
|
||||
Detail: r.Detail, CreatedAt: r.CreatedAt, Email: r.Email, TargetMail: r.TargetMail,
|
||||
})
|
||||
for i := range rows {
|
||||
out = append(out, ComplaintRow(rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@ -23,12 +23,8 @@ type groupScanRow struct {
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
func groupScanToRow(r groupScanRow) GroupRow {
|
||||
return GroupRow{
|
||||
ID: r.ID, ConversationID: r.ConversationID, Name: r.Name, Avatar: r.Avatar,
|
||||
OwnerID: r.OwnerID, MutedAll: r.MutedAll, MemberCount: r.MemberCount, Status: r.Status,
|
||||
CreatedAt: r.CreatedAt, UpdatedAt: r.UpdatedAt,
|
||||
}
|
||||
func groupScanToRow(r *groupScanRow) GroupRow {
|
||||
return GroupRow(*r)
|
||||
}
|
||||
|
||||
func (m *Repo) ListGroups(ctx context.Context, q string) ([]GroupRow, error) {
|
||||
@ -51,8 +47,8 @@ func (m *Repo) ListGroups(ctx context.Context, q string) ([]GroupRow, error) {
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]GroupRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, groupScanToRow(r))
|
||||
for i := range rows {
|
||||
out = append(out, groupScanToRow(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -66,7 +62,7 @@ func (m *Repo) GroupByID(ctx context.Context, groupID int64) (*GroupRow, error)
|
||||
if err := db.Table("`t_groups`").Where("id = ?", groupID).Take(&r).Error; err != nil {
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
row := groupScanToRow(r)
|
||||
row := groupScanToRow(&r)
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
@ -115,10 +111,8 @@ LIMIT 500`, groupID).Scan(&rows).Error
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]GroupMemberRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, GroupMemberRow{
|
||||
UserID: r.UserID, Role: r.Role, Nickname: r.Nickname, Email: r.Email, UNick: r.UNick,
|
||||
})
|
||||
for i := range rows {
|
||||
out = append(out, GroupMemberRow(rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@ -56,13 +56,8 @@ FROM t_group_message`
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]MessageRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, MessageRow{
|
||||
ID: r.ID, ConversationID: r.ConversationID, SenderID: r.SenderID,
|
||||
ReceiverID: r.ReceiverID, GroupID: r.GroupID, ClientMsgID: r.ClientMsgID,
|
||||
MessageType: r.MessageType, Content: r.Content, Status: r.Status,
|
||||
CreatedAt: r.CreatedAt, ChatType: r.ChatType,
|
||||
})
|
||||
for i := range rows {
|
||||
out = append(out, MessageRow(rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -98,11 +93,8 @@ LIMIT 200`, userID).Scan(&rows).Error
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]ConversationRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, ConversationRow{
|
||||
ID: r.ID, Type: r.Type, LastMsgID: r.LastMsgID, LastMsgAt: r.LastMsgAt,
|
||||
UserSmall: r.UserSmall, UserBig: r.UserBig, GroupID: r.GroupID, GroupName: r.GroupName,
|
||||
})
|
||||
for i := range rows {
|
||||
out = append(out, ConversationRow(rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -116,6 +108,7 @@ func (m *Repo) ListGroupMessages(ctx context.Context, groupID int64, limit int)
|
||||
ID int64 `gorm:"column:id"`
|
||||
ConversationID int64 `gorm:"column:conversation_id"`
|
||||
SenderID int64 `gorm:"column:sender_id"`
|
||||
ReceiverID int64 `gorm:"column:receiver_id"`
|
||||
GroupID int64 `gorm:"column:group_id"`
|
||||
ClientMsgID string `gorm:"column:client_msg_id"`
|
||||
MessageType int16 `gorm:"column:message_type"`
|
||||
@ -126,18 +119,14 @@ func (m *Repo) ListGroupMessages(ctx context.Context, groupID int64, limit int)
|
||||
}
|
||||
var rows []scanRow
|
||||
err = db.Raw(`
|
||||
SELECT id, conversation_id, sender_id, group_id, client_msg_id, message_type, content, status, created_at, 'group' AS chat_type
|
||||
SELECT id, conversation_id, sender_id, 0 AS receiver_id, group_id, client_msg_id, message_type, content, status, created_at, 'group' AS chat_type
|
||||
FROM t_group_message WHERE group_id = ? ORDER BY created_at DESC LIMIT ?`, groupID, limit).Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]MessageRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, MessageRow{
|
||||
ID: r.ID, ConversationID: r.ConversationID, SenderID: r.SenderID, GroupID: r.GroupID,
|
||||
ClientMsgID: r.ClientMsgID, MessageType: r.MessageType, Content: r.Content,
|
||||
Status: r.Status, CreatedAt: r.CreatedAt, ChatType: r.ChatType,
|
||||
})
|
||||
for i := range rows {
|
||||
out = append(out, MessageRow(rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ WHERE u.id = ?`, id).Scan(&rows).Error
|
||||
if len(rows) == 0 || rows[0].ID == 0 {
|
||||
return nil, errs.ErrNotFound
|
||||
}
|
||||
r := userScanToRow(rows[0])
|
||||
r := userScanToRow(&rows[0])
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
@ -223,8 +223,8 @@ LIMIT ?`, limit).Scan(&rows).Error
|
||||
return nil, mapDBErr(err)
|
||||
}
|
||||
out := make([]SameIPRankRow, len(rows))
|
||||
for i, r := range rows {
|
||||
out[i] = SameIPRankRow{IP: r.IP, UserCount: r.UserCount, DeviceCount: r.DeviceCount}
|
||||
for i := range rows {
|
||||
out[i] = SameIPRankRow{IP: rows[i].IP, UserCount: rows[i].UserCount, DeviceCount: rows[i].DeviceCount}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -254,16 +254,12 @@ type userScanRow struct {
|
||||
|
||||
func userScanToRows(rows []userScanRow) []UserRow {
|
||||
out := make([]UserRow, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, userScanToRow(r))
|
||||
for i := range rows {
|
||||
out = append(out, userScanToRow(&rows[i]))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func userScanToRow(r userScanRow) UserRow {
|
||||
return UserRow{
|
||||
ID: r.ID, SparkID: r.SparkID, Nickname: r.Nickname, Avatar: r.Avatar,
|
||||
Status: r.Status, LastActiveAt: r.LastActiveAt, CreatedAt: r.CreatedAt,
|
||||
Email: r.Email, DeviceID: r.DeviceID, Platform: r.Platform,
|
||||
}
|
||||
func userScanToRow(r *userScanRow) UserRow {
|
||||
return UserRow(*r)
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ func (m *Repo) Create(ctx context.Context, message *entity.Message) error {
|
||||
message.Kind = kind
|
||||
row := rowFrom(message)
|
||||
stampCreatedAt(message, &row)
|
||||
return m.insertBody(db, message, row)
|
||||
return m.insertBody(db, message, &row)
|
||||
}
|
||||
|
||||
func (m *Repo) GetByClientMsgID(ctx context.Context, senderID, conversationID int64, clientMsgID string) (*entity.Message, error) {
|
||||
@ -151,7 +151,7 @@ func (m *Repo) CreateAndBump(ctx context.Context, message *entity.Message) error
|
||||
return err
|
||||
}
|
||||
message.Kind = kind
|
||||
if err := m.insertBody(tx, message, row); err != nil {
|
||||
if err := m.insertBody(tx, message, &row); err != nil {
|
||||
return err
|
||||
}
|
||||
res := tx.Table("t_conversations").Where("id = ?", message.ConversationID).Updates(map[string]any{
|
||||
@ -177,7 +177,7 @@ func stampCreatedAt(message *entity.Message, row *MessageRow) {
|
||||
message.CreatedAtMS = row.CreatedAt.UnixMilli()
|
||||
}
|
||||
|
||||
func (m *Repo) insertBody(db *gorm.DB, message *entity.Message, row MessageRow) error {
|
||||
func (m *Repo) insertBody(db *gorm.DB, message *entity.Message, row *MessageRow) error {
|
||||
if message.Kind == entity.KindGroup {
|
||||
if err := m.fillGroupID(db, message); err != nil {
|
||||
return err
|
||||
|
||||
@ -55,12 +55,12 @@ func rowFrom(m *entity.Message) MessageRow {
|
||||
}
|
||||
}
|
||||
|
||||
func privateRow(m *entity.Message, base MessageRow) PrivateMessage {
|
||||
return PrivateMessage{MessageRow: base, ReceiverID: m.ReceiverID}
|
||||
func privateRow(m *entity.Message, base *MessageRow) PrivateMessage {
|
||||
return PrivateMessage{MessageRow: *base, ReceiverID: m.ReceiverID}
|
||||
}
|
||||
|
||||
func groupRow(m *entity.Message, base MessageRow) GroupMessage {
|
||||
return GroupMessage{MessageRow: base, GroupID: m.GroupID}
|
||||
func groupRow(m *entity.Message, base *MessageRow) GroupMessage {
|
||||
return GroupMessage{MessageRow: *base, GroupID: m.GroupID}
|
||||
}
|
||||
|
||||
func tableOf(kind entity.Kind) string {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
smsentity "spark-server/internal/module/sms/entity"
|
||||
"spark-server/internal/module/meta/entity"
|
||||
smsentity "spark-server/internal/module/sms/entity"
|
||||
"spark-server/internal/pkg/i18n"
|
||||
)
|
||||
|
||||
|
||||
@ -15,6 +15,21 @@ import (
|
||||
|
||||
var ErrNotReady = errors.New("music: service not ready")
|
||||
|
||||
const (
|
||||
mimeJPEG = "image/jpeg"
|
||||
mimePNG = "image/png"
|
||||
mimeWebP = "image/webp"
|
||||
mimeGIF = "image/gif"
|
||||
mimeMPEG = "audio/mpeg"
|
||||
|
||||
extJPG = "jpg"
|
||||
extJPEG = "jpeg"
|
||||
extPNG = "png"
|
||||
extWebP = "webp"
|
||||
extGIF = "gif"
|
||||
extMP3 = "mp3"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
ListAll(ctx context.Context) ([]entity.Track, error)
|
||||
ListEnabled(ctx context.Context) ([]entity.Track, error)
|
||||
@ -259,7 +274,7 @@ func (s *Service) uploadImage(ctx context.Context, id int64, filename string, bo
|
||||
if err != nil {
|
||||
return nil, errs.ErrNotFound.WithMsg("曲目不存在")
|
||||
}
|
||||
ext := extFor(ct, filename, "jpg")
|
||||
ext := extFor(ct, filename, extJPG)
|
||||
key := fmt.Sprintf("perm/music/%s/%d/%d.%s", kind, id, time.Now().UnixNano(), ext)
|
||||
if err := s.put(ctx, key, body, ct, maxBytes); err != nil {
|
||||
return nil, err
|
||||
@ -301,7 +316,7 @@ func (s *Service) UploadAudio(ctx context.Context, id int64, filename string, bo
|
||||
if err != nil {
|
||||
return nil, errs.ErrNotFound.WithMsg("曲目不存在")
|
||||
}
|
||||
ext := extFor(ct, filename, "mp3")
|
||||
ext := extFor(ct, filename, extMP3)
|
||||
key := fmt.Sprintf("perm/music/audio/%d/%d.%s", id, time.Now().UnixNano(), ext)
|
||||
if err := s.put(ctx, key, body, ct, entity.MaxAudioBytes); err != nil {
|
||||
return nil, err
|
||||
@ -393,11 +408,12 @@ func (s *Service) UpdateCategory(ctx context.Context, id int64, in CategoryInput
|
||||
return nil, errs.ErrInvalidArgument.WithMsg("name 不能为空")
|
||||
}
|
||||
if in.ParentID != nil {
|
||||
if *in.ParentID == 0 {
|
||||
switch {
|
||||
case *in.ParentID == 0:
|
||||
c.ParentID = nil
|
||||
} else if *in.ParentID == id {
|
||||
case *in.ParentID == id:
|
||||
return nil, errs.ErrInvalidArgument.WithMsg("不能将分类设为自己的父级")
|
||||
} else {
|
||||
default:
|
||||
parent, err := s.repo.GetCategory(ctx, *in.ParentID)
|
||||
if err != nil {
|
||||
return nil, errs.ErrNotFound.WithMsg("父分类不存在")
|
||||
@ -458,11 +474,10 @@ func (s *Service) applyTrackCategories(ctx context.Context, trackID int64, categ
|
||||
func buildCategoryTree(flat []entity.Category) []entity.Category {
|
||||
nodes := make(map[int64]*entity.Category, len(flat))
|
||||
order := make([]int64, 0, len(flat))
|
||||
for _, c := range flat {
|
||||
cc := c
|
||||
cc.Children = nil
|
||||
nodes[cc.ID] = &cc
|
||||
order = append(order, cc.ID)
|
||||
for i := range flat {
|
||||
flat[i].Children = nil
|
||||
nodes[flat[i].ID] = &flat[i]
|
||||
order = append(order, flat[i].ID)
|
||||
}
|
||||
rootPtrs := make([]*entity.Category, 0)
|
||||
for _, id := range order {
|
||||
@ -501,11 +516,11 @@ func dedupeIDs(ids []int64) []int64 {
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Service) put(ctx context.Context, key string, body []byte, contentType string, max int) error {
|
||||
func (s *Service) put(ctx context.Context, key string, body []byte, contentType string, maxBytes int) error {
|
||||
if s.objects == nil || !s.objects.Ready() {
|
||||
return errs.ErrAvailable.WithMsg("对象存储未就绪")
|
||||
}
|
||||
return s.objects.PutLarge(ctx, key, body, contentType, max)
|
||||
return s.objects.PutLarge(ctx, key, body, contentType, maxBytes)
|
||||
}
|
||||
|
||||
func (s *Service) deleteObject(ctx context.Context, key string) error {
|
||||
@ -524,16 +539,16 @@ func normalizeMIME(raw, filename string) string {
|
||||
}
|
||||
ext := strings.ToLower(strings.TrimPrefix(path.Ext(filename), "."))
|
||||
switch ext {
|
||||
case "jpg", "jpeg":
|
||||
return "image/jpeg"
|
||||
case "png":
|
||||
return "image/png"
|
||||
case "webp":
|
||||
return "image/webp"
|
||||
case "gif":
|
||||
return "image/gif"
|
||||
case "mp3":
|
||||
return "audio/mpeg"
|
||||
case extJPG, extJPEG:
|
||||
return mimeJPEG
|
||||
case extPNG:
|
||||
return mimePNG
|
||||
case extWebP:
|
||||
return mimeWebP
|
||||
case extGIF:
|
||||
return mimeGIF
|
||||
case extMP3:
|
||||
return mimeMPEG
|
||||
case "m4a":
|
||||
return "audio/mp4"
|
||||
default:
|
||||
@ -565,15 +580,15 @@ func extFor(ct, filename, fallback string) string {
|
||||
}
|
||||
switch ct {
|
||||
case "image/jpeg", "image/jpg":
|
||||
return "jpg"
|
||||
case "image/png":
|
||||
return "png"
|
||||
case "image/webp":
|
||||
return "webp"
|
||||
case "image/gif":
|
||||
return "gif"
|
||||
return extJPG
|
||||
case mimePNG:
|
||||
return extPNG
|
||||
case mimeWebP:
|
||||
return extWebP
|
||||
case mimeGIF:
|
||||
return extGIF
|
||||
case "audio/mpeg", "audio/mp3":
|
||||
return "mp3"
|
||||
return extMP3
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
|
||||
374
internal/module/music/logic/service_test.go
Normal file
374
internal/module/music/logic/service_test.go
Normal file
@ -0,0 +1,374 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"spark-server/internal/module/music/entity"
|
||||
"spark-server/internal/pkg/errs"
|
||||
)
|
||||
|
||||
type stubMusicRepo struct {
|
||||
tracks map[int64]*entity.Track
|
||||
categories map[int64]*entity.Category
|
||||
trackCategories map[int64][]int64
|
||||
nextID int64
|
||||
nextCatID int64
|
||||
preview int
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) ensureCats() {
|
||||
if s.categories == nil {
|
||||
s.categories = map[int64]*entity.Category{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) ListAll(_ context.Context) ([]entity.Track, error) {
|
||||
out := make([]entity.Track, 0, len(s.tracks))
|
||||
for _, t := range s.tracks {
|
||||
out = append(out, *t)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) ListEnabled(_ context.Context) ([]entity.Track, error) {
|
||||
out := make([]entity.Track, 0)
|
||||
for _, t := range s.tracks {
|
||||
if t.Enabled {
|
||||
out = append(out, *t)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) ListEnabledByCategory(_ context.Context, _ int64) ([]entity.Track, error) {
|
||||
return s.ListEnabled(context.Background())
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) Get(_ context.Context, id int64) (*entity.Track, error) {
|
||||
t, ok := s.tracks[id]
|
||||
if !ok {
|
||||
return nil, errs.ErrNotFound
|
||||
}
|
||||
cp := *t
|
||||
if s.trackCategories != nil {
|
||||
cp.CategoryIDs = append([]int64(nil), s.trackCategories[id]...)
|
||||
}
|
||||
return &cp, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) Insert(_ context.Context, t *entity.Track) error {
|
||||
if s.tracks == nil {
|
||||
s.tracks = map[int64]*entity.Track{}
|
||||
}
|
||||
s.nextID++
|
||||
t.ID = s.nextID
|
||||
cp := *t
|
||||
s.tracks[t.ID] = &cp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) Update(_ context.Context, t *entity.Track) error {
|
||||
if s.tracks[t.ID] == nil {
|
||||
return errs.ErrNotFound
|
||||
}
|
||||
cp := *t
|
||||
s.tracks[t.ID] = &cp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) Delete(_ context.Context, id int64) error {
|
||||
if s.tracks[id] == nil {
|
||||
return errs.ErrNotFound
|
||||
}
|
||||
delete(s.tracks, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) MemberPreviewSeconds(context.Context) (int, error) {
|
||||
if s.preview == 0 {
|
||||
return entity.DefaultPreviewSeconds, nil
|
||||
}
|
||||
return s.preview, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) SetSetting(context.Context, string, string) error { return nil }
|
||||
|
||||
func (s *stubMusicRepo) ListCategories(_ context.Context) ([]entity.Category, error) {
|
||||
s.ensureCats()
|
||||
out := make([]entity.Category, 0, len(s.categories))
|
||||
for _, c := range s.categories {
|
||||
out = append(out, *c)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) ListEnabledCategories(ctx context.Context) ([]entity.Category, error) {
|
||||
all, err := s.ListCategories(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]entity.Category, 0)
|
||||
for i := range all {
|
||||
if all[i].Enabled {
|
||||
out = append(out, all[i])
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) GetCategory(_ context.Context, id int64) (*entity.Category, error) {
|
||||
s.ensureCats()
|
||||
c, ok := s.categories[id]
|
||||
if !ok {
|
||||
return nil, errs.ErrNotFound
|
||||
}
|
||||
cp := *c
|
||||
return &cp, nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) InsertCategory(_ context.Context, c *entity.Category) error {
|
||||
s.ensureCats()
|
||||
s.nextCatID++
|
||||
c.ID = s.nextCatID
|
||||
cp := *c
|
||||
s.categories[c.ID] = &cp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) UpdateCategory(_ context.Context, c *entity.Category) error {
|
||||
s.ensureCats()
|
||||
if s.categories[c.ID] == nil {
|
||||
return errs.ErrNotFound
|
||||
}
|
||||
cp := *c
|
||||
s.categories[c.ID] = &cp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) DeleteCategory(_ context.Context, id int64) error {
|
||||
s.ensureCats()
|
||||
if s.categories[id] == nil {
|
||||
return errs.ErrNotFound
|
||||
}
|
||||
delete(s.categories, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stubMusicRepo) SetTrackCategories(_ context.Context, trackID int64, categoryIDs []int64) error {
|
||||
if s.trackCategories == nil {
|
||||
s.trackCategories = map[int64][]int64{}
|
||||
}
|
||||
s.trackCategories[trackID] = append([]int64(nil), categoryIDs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubObjects struct {
|
||||
ready bool
|
||||
}
|
||||
|
||||
func (o stubObjects) Ready() bool { return o.ready }
|
||||
|
||||
func (o stubObjects) PutLarge(context.Context, string, []byte, string, int) error { return nil }
|
||||
|
||||
func (o stubObjects) Delete(context.Context, string) error { return nil }
|
||||
|
||||
func newTestService() (*Service, *stubMusicRepo) {
|
||||
repo := &stubMusicRepo{preview: 30, tracks: map[int64]*entity.Track{}}
|
||||
return NewService(repo, stubObjects{ready: true}, "https://cdn.example", "spark"), repo
|
||||
}
|
||||
|
||||
func TestServicePlaylistAndSettings(t *testing.T) {
|
||||
svc, repo := newTestService()
|
||||
_ = repo.Insert(context.Background(), &entity.Track{Title: "a", Enabled: true})
|
||||
pl, err := svc.Playlist(context.Background(), 0)
|
||||
if err != nil || len(pl.Items) != 1 || pl.MemberPreviewSeconds != 30 {
|
||||
t.Fatalf("playlist %+v %v", pl, err)
|
||||
}
|
||||
st, err := svc.Settings(context.Background())
|
||||
if err != nil || st.MemberPreviewSeconds != 30 {
|
||||
t.Fatalf("settings %+v %v", st, err)
|
||||
}
|
||||
all, err := svc.ListAdmin(context.Background())
|
||||
if err != nil || len(all) != 1 {
|
||||
t.Fatalf("admin list %+v %v", all, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceCRUD(t *testing.T) {
|
||||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
created, err := svc.Create(ctx, UpsertInput{Title: " hi "})
|
||||
if err != nil || created.ID != 1 || created.Title != "hi" {
|
||||
t.Fatalf("create %+v %v", created, err)
|
||||
}
|
||||
req := true
|
||||
updated, err := svc.Update(ctx, 1, UpsertInput{Title: "new", RequireMember: &req})
|
||||
if err != nil || !updated.RequireMember || updated.Title != "new" {
|
||||
t.Fatalf("update %+v %v", updated, err)
|
||||
}
|
||||
if err := svc.Delete(ctx, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := svc.Delete(ctx, 1); err == nil {
|
||||
t.Fatal("want not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceUpdateSettingsValidation(t *testing.T) {
|
||||
svc, _ := newTestService()
|
||||
_, err := svc.UpdateSettings(context.Background(), -1)
|
||||
var ae *errs.AppError
|
||||
if !errors.As(err, &ae) || ae.Code != errs.ErrInvalidArgument.Code {
|
||||
t.Fatalf("want invalid %v", err)
|
||||
}
|
||||
ok, err := svc.UpdateSettings(context.Background(), 45)
|
||||
if err != nil || ok.MemberPreviewSeconds != 45 {
|
||||
t.Fatalf("settings %+v %v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceUploadValidation(t *testing.T) {
|
||||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
if _, err := svc.Create(ctx, UpsertInput{Title: ""}); err == nil {
|
||||
t.Fatal("empty title")
|
||||
}
|
||||
if _, err := svc.UploadCover(ctx, 1, "x.jpg", nil, ""); err == nil {
|
||||
t.Fatal("missing track")
|
||||
}
|
||||
track, _ := svc.Create(ctx, UpsertInput{Title: "t"})
|
||||
if _, err := svc.UploadCover(ctx, track.ID, "x.jpg", nil, ""); err == nil {
|
||||
t.Fatal("empty body")
|
||||
}
|
||||
if _, err := svc.UploadCover(ctx, track.ID, "x.bin", []byte("x"), "application/octet-stream"); err == nil {
|
||||
t.Fatal("bad mime")
|
||||
}
|
||||
if _, err := svc.UploadAudio(ctx, track.ID, "a.mp3", []byte{1, 2}, mimeMPEG); err != nil {
|
||||
t.Fatalf("audio %v", err)
|
||||
}
|
||||
got, err := svc.repo.Get(ctx, track.ID)
|
||||
if err != nil || got.AudioKey == "" || got.AudioURL == "" {
|
||||
t.Fatalf("track %+v %v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeMIMEAndHelpers(t *testing.T) {
|
||||
if got := normalizeMIME("", "photo.JPG"); got != mimeJPEG {
|
||||
t.Fatalf("jpg %q", got)
|
||||
}
|
||||
if got := normalizeMIME("image/png", ""); got != mimePNG {
|
||||
t.Fatalf("png %q", got)
|
||||
}
|
||||
if !isImageMIME(mimeGIF) || isImageMIME("text/plain") {
|
||||
t.Fatal("image mime")
|
||||
}
|
||||
if !isAudioMIME(mimeMPEG, "a.mp3") || !isAudioMIME("", "song.mp3") {
|
||||
t.Fatal("audio mime")
|
||||
}
|
||||
if extFor(mimePNG, "", "jpg") != "png" || extFor("", "file.weB", "bin") != "web" {
|
||||
t.Fatal("extFor")
|
||||
}
|
||||
svc := NewService(&stubMusicRepo{}, nil, "https://cdn.example", "spark")
|
||||
if u := svc.publicURL("perm/a.jpg"); u != "https://cdn.example/spark/perm/a.jpg" {
|
||||
t.Fatalf("url %q", u)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceUploadBackground(t *testing.T) {
|
||||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
track, err := svc.Create(ctx, UpsertInput{Title: "bg"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := []byte{0xff, 0xd8, 0xff}
|
||||
updated, err := svc.UploadBackground(ctx, track.ID, "bg.jpg", body, mimeJPEG)
|
||||
if err != nil || updated.BackgroundKey == "" || updated.BackgroundURL == "" {
|
||||
t.Fatalf("background %+v %v", updated, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicePutWithoutStorage(t *testing.T) {
|
||||
repo := &stubMusicRepo{tracks: map[int64]*entity.Track{1: {ID: 1, Title: "x"}}}
|
||||
svc := NewService(repo, stubObjects{ready: false}, "", "spark")
|
||||
_, err := svc.UploadAudio(context.Background(), 1, "a.mp3", []byte{1}, mimeMPEG)
|
||||
if err == nil {
|
||||
t.Fatal("want storage error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServicePublicURLDefaults(t *testing.T) {
|
||||
svc := NewService(&stubMusicRepo{}, nil, "https://x", "")
|
||||
if svc.publicURL("k") != "https://x/spark/k" {
|
||||
t.Fatalf("%q", svc.publicURL("k"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceNotReady(t *testing.T) {
|
||||
var svc *Service
|
||||
if _, err := svc.Playlist(context.Background(), 0); !errors.Is(err, ErrNotReady) {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceCategories(t *testing.T) {
|
||||
svc, _ := newTestService()
|
||||
ctx := context.Background()
|
||||
|
||||
root, err := svc.CreateCategory(ctx, CategoryInput{Name: "Root"})
|
||||
if err != nil || root.ID != 1 {
|
||||
t.Fatalf("root %+v %v", root, err)
|
||||
}
|
||||
pid := root.ID
|
||||
child, err := svc.CreateCategory(ctx, CategoryInput{Name: "Child", ParentID: &pid})
|
||||
if err != nil || child.ParentID == nil || *child.ParentID != root.ID {
|
||||
t.Fatalf("child %+v %v", child, err)
|
||||
}
|
||||
if _, err := svc.CreateCategory(ctx, CategoryInput{Name: "bad", ParentID: &child.ID}); err == nil {
|
||||
t.Fatal("want depth limit")
|
||||
}
|
||||
tree, err := svc.CategoryTree(ctx, true)
|
||||
if err != nil || len(tree) != 1 || len(tree[0].Children) != 1 {
|
||||
t.Fatalf("tree %+v %v", tree, err)
|
||||
}
|
||||
if _, err := svc.ListCategoriesAdmin(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dis := false
|
||||
_, err = svc.UpdateCategory(ctx, root.ID, CategoryInput{ParentID: int64Ptr(0), Enabled: &dis})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
track, err := svc.Create(ctx, UpsertInput{Title: "t", CategoryIDs: []int64{root.ID, root.ID, 0}})
|
||||
if err != nil || len(track.CategoryIDs) != 1 {
|
||||
t.Fatalf("track cats %+v %v", track, err)
|
||||
}
|
||||
updated, err := svc.SetTrackCategories(ctx, track.ID, []int64{child.ID})
|
||||
if err != nil || len(updated.CategoryIDs) != 1 || updated.CategoryIDs[0] != child.ID {
|
||||
t.Fatalf("set cats %+v %v", updated, err)
|
||||
}
|
||||
if err := svc.DeleteCategory(ctx, root.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func int64Ptr(v int64) *int64 { return &v }
|
||||
|
||||
func TestDedupeIDs(t *testing.T) {
|
||||
got := dedupeIDs([]int64{1, 1, 0, -1, 2, 2})
|
||||
if len(got) != 2 || got[0] != 1 || got[1] != 2 {
|
||||
t.Fatalf("%v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCategoryTreeOrphan(t *testing.T) {
|
||||
tree := buildCategoryTree([]entity.Category{
|
||||
{ID: 2, Name: "orphan", ParentID: int64Ptr(99)},
|
||||
{ID: 1, Name: "root"},
|
||||
})
|
||||
if len(tree) != 2 {
|
||||
t.Fatalf("%+v", tree)
|
||||
}
|
||||
}
|
||||
@ -82,8 +82,8 @@ func (r *Repo) ListAll(ctx context.Context) ([]entity.Track, error) {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]entity.Track, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, toEntity(row))
|
||||
for i := range rows {
|
||||
out = append(out, toEntity(&rows[i]))
|
||||
}
|
||||
return r.attachCategoryIDs(ctx, out)
|
||||
}
|
||||
@ -98,8 +98,8 @@ func (r *Repo) ListEnabled(ctx context.Context) ([]entity.Track, error) {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]entity.Track, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, toEntity(row))
|
||||
for i := range rows {
|
||||
out = append(out, toEntity(&rows[i]))
|
||||
}
|
||||
return r.attachCategoryIDs(ctx, out)
|
||||
}
|
||||
@ -131,8 +131,8 @@ func (r *Repo) ListEnabledByCategory(ctx context.Context, categoryID int64) ([]e
|
||||
return nil, err
|
||||
}
|
||||
out := make([]entity.Track, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, toEntity(row))
|
||||
for i := range rows {
|
||||
out = append(out, toEntity(&rows[i]))
|
||||
}
|
||||
return r.attachCategoryIDs(ctx, out)
|
||||
}
|
||||
@ -143,7 +143,8 @@ func (r *Repo) categoryScopeIDs(ctx context.Context, categoryID int64) ([]int64,
|
||||
return nil, err
|
||||
}
|
||||
byParent := map[int64][]int64{}
|
||||
for _, c := range all {
|
||||
for i := range all {
|
||||
c := &all[i]
|
||||
if c.ParentID != nil {
|
||||
byParent[*c.ParentID] = append(byParent[*c.ParentID], c.ID)
|
||||
}
|
||||
@ -170,7 +171,7 @@ func (r *Repo) Get(ctx context.Context, id int64) (*entity.Track, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := toEntity(row)
|
||||
t := toEntity(&row)
|
||||
ids, err := r.TrackCategoryIDs(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -292,8 +293,8 @@ func (r *Repo) ListCategories(ctx context.Context) ([]entity.Category, error) {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]entity.Category, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, toCategoryEntity(row))
|
||||
for i := range rows {
|
||||
out = append(out, toCategoryEntity(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -308,8 +309,8 @@ func (r *Repo) ListEnabledCategories(ctx context.Context) ([]entity.Category, er
|
||||
return nil, err
|
||||
}
|
||||
out := make([]entity.Category, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, toCategoryEntity(row))
|
||||
for i := range rows {
|
||||
out = append(out, toCategoryEntity(&rows[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -323,7 +324,7 @@ func (r *Repo) GetCategory(ctx context.Context, id int64) (*entity.Category, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := toCategoryEntity(row)
|
||||
c := toCategoryEntity(&row)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
@ -434,8 +435,8 @@ func (r *Repo) attachCategoryIDs(ctx context.Context, tracks []entity.Track) ([]
|
||||
return tracks, nil
|
||||
}
|
||||
ids := make([]int64, 0, len(tracks))
|
||||
for _, t := range tracks {
|
||||
ids = append(ids, t.ID)
|
||||
for i := range tracks {
|
||||
ids = append(ids, tracks[i].ID)
|
||||
}
|
||||
var links []categoryTrackRow
|
||||
err := r.db.WithContext(ctx).Where("track_id IN ?", ids).Order("category_id ASC").Find(&links).Error
|
||||
@ -443,7 +444,8 @@ func (r *Repo) attachCategoryIDs(ctx context.Context, tracks []entity.Track) ([]
|
||||
return nil, err
|
||||
}
|
||||
m := map[int64][]int64{}
|
||||
for _, l := range links {
|
||||
for i := range links {
|
||||
l := &links[i]
|
||||
m[l.TrackID] = append(m[l.TrackID], l.CategoryID)
|
||||
}
|
||||
for i := range tracks {
|
||||
@ -456,7 +458,7 @@ func (r *Repo) attachCategoryIDs(ctx context.Context, tracks []entity.Track) ([]
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
func toEntity(row trackRow) entity.Track {
|
||||
func toEntity(row *trackRow) entity.Track {
|
||||
return entity.Track{
|
||||
ID: row.ID,
|
||||
Title: row.Title,
|
||||
@ -475,7 +477,7 @@ func toEntity(row trackRow) entity.Track {
|
||||
}
|
||||
}
|
||||
|
||||
func toCategoryEntity(row categoryRow) entity.Category {
|
||||
func toCategoryEntity(row *categoryRow) entity.Category {
|
||||
return entity.Category{
|
||||
ID: row.ID,
|
||||
ParentID: row.ParentID,
|
||||
|
||||
@ -105,7 +105,8 @@ type sampleSpec struct {
|
||||
func (s *Service) ensureSampleMessages(ctx context.Context, g *groupentity.Group, bots []*userentity.User) (int, error) {
|
||||
specs := sampleSpecs()
|
||||
n := 0
|
||||
for _, spec := range specs {
|
||||
for i := range specs {
|
||||
spec := specs[i]
|
||||
senderID := int64(0)
|
||||
if spec.Type != msgentity.TypeSystem {
|
||||
senderID = bots[spec.Sender%len(bots)].ID
|
||||
|
||||
@ -6,12 +6,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"spark-server/internal/infrastructure/id"
|
||||
smslogic "spark-server/internal/module/sms/logic"
|
||||
smsmodel "spark-server/internal/module/sms/model"
|
||||
friendlogic "spark-server/internal/module/friend/logic"
|
||||
friendmodel "spark-server/internal/module/friend/model"
|
||||
momentlogic "spark-server/internal/module/moment/logic"
|
||||
momentmodel "spark-server/internal/module/moment/model"
|
||||
smslogic "spark-server/internal/module/sms/logic"
|
||||
smsmodel "spark-server/internal/module/sms/model"
|
||||
"spark-server/internal/module/user/entity"
|
||||
"spark-server/internal/module/user/model"
|
||||
"spark-server/internal/pkg/errs"
|
||||
|
||||
@ -11,9 +11,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"spark-server/internal/infrastructure/id"
|
||||
smsentity "spark-server/internal/module/sms/entity"
|
||||
smslogic "spark-server/internal/module/sms/logic"
|
||||
smsmodel "spark-server/internal/module/sms/model"
|
||||
smsentity "spark-server/internal/module/sms/entity"
|
||||
"spark-server/internal/module/user/entity"
|
||||
"spark-server/internal/module/user/model"
|
||||
"spark-server/internal/pkg/errs"
|
||||
|
||||
@ -10,12 +10,12 @@ import (
|
||||
)
|
||||
|
||||
type UserDevice struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)"`
|
||||
UserID int64 `gorm:"column:user_id;comment:所属用户"`
|
||||
DeviceID uint64 `gorm:"column:device_id;comment:devices.device_id,JWT did"`
|
||||
LastLoginAt time.Time `gorm:"column:last_login_at;comment:该用户在此设备最近登录成功"`
|
||||
LastActiveAt *time.Time `gorm:"column:last_active_at;comment:该用户在此设备最近活跃"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;comment:该用户首次在此设备绑定"`
|
||||
ID int64 `gorm:"column:id;primaryKey;autoIncrement;comment:行 id,表内自增,仅内部用;业务唯一键是 (user_id, device_id)"`
|
||||
UserID int64 `gorm:"column:user_id;comment:所属用户"`
|
||||
DeviceID uint64 `gorm:"column:device_id;comment:devices.device_id,JWT did"`
|
||||
LastLoginAt time.Time `gorm:"column:last_login_at;comment:该用户在此设备最近登录成功"`
|
||||
LastActiveAt *time.Time `gorm:"column:last_active_at;comment:该用户在此设备最近活跃"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;comment:该用户首次在此设备绑定"`
|
||||
}
|
||||
|
||||
func (UserDevice) TableName() string { return "t_user_devices" }
|
||||
|
||||
@ -2,27 +2,27 @@ package errs
|
||||
|
||||
// Sentinel AppErrors. Code is the envelope code shared by HTTP and TCP.
|
||||
var (
|
||||
OK = NewError("0", "ok", "")
|
||||
ErrInvalidArgument = NewError("40001", "invalid argument", "")
|
||||
ErrFrameTooLarge = NewError("40002", "frame too large", "")
|
||||
ErrInvalidFrame = NewError("40003", "invalid frame", "")
|
||||
ErrProxyHeader = NewError("40004", "invalid proxy protocol header", "")
|
||||
ErrUnauthorized = NewError("40101", "unauthorized", "")
|
||||
ErrNotAuthenticated = NewError("40102", "not authenticated", "")
|
||||
OK = NewError("0", "ok", "")
|
||||
ErrInvalidArgument = NewError("40001", "invalid argument", "")
|
||||
ErrFrameTooLarge = NewError("40002", "frame too large", "")
|
||||
ErrInvalidFrame = NewError("40003", "invalid frame", "")
|
||||
ErrProxyHeader = NewError("40004", "invalid proxy protocol header", "")
|
||||
ErrUnauthorized = NewError("40101", "unauthorized", "")
|
||||
ErrNotAuthenticated = NewError("40102", "not authenticated", "")
|
||||
ErrVerifyCodeInvalid = NewError("40103", "verification code invalid", "")
|
||||
ErrForbidden = NewError("40301", "forbidden", "")
|
||||
ErrNotFound = NewError("40401", "not found", "")
|
||||
ErrMediaExpired = NewError("40402", "media expired", "")
|
||||
ErrDuplicate = NewError("40901", "duplicate", "")
|
||||
ErrBusy = NewError("42901", "busy", "")
|
||||
ErrRateLimited = NewError("42902", "rate limited", "")
|
||||
ErrOutboundDropped = NewError("42903", "outbound dropped", "")
|
||||
ErrClosed = NewError("49901", "connection closed", "")
|
||||
ErrInner = NewErrorPrint("50001", "internal", "")
|
||||
ErrOperatFail = NewErrorPrint("50002", "operation failed", "")
|
||||
ErrClockRewind = NewErrorPrint("50003", "snowflake clock rewind", "")
|
||||
ErrAvailable = NewErrorPrint("50301", "unavailable", "")
|
||||
ErrTimeout = NewErrorPrint("50401", "timeout", "")
|
||||
ErrForbidden = NewError("40301", "forbidden", "")
|
||||
ErrNotFound = NewError("40401", "not found", "")
|
||||
ErrMediaExpired = NewError("40402", "media expired", "")
|
||||
ErrDuplicate = NewError("40901", "duplicate", "")
|
||||
ErrBusy = NewError("42901", "busy", "")
|
||||
ErrRateLimited = NewError("42902", "rate limited", "")
|
||||
ErrOutboundDropped = NewError("42903", "outbound dropped", "")
|
||||
ErrClosed = NewError("49901", "connection closed", "")
|
||||
ErrInner = NewErrorPrint("50001", "internal", "")
|
||||
ErrOperatFail = NewErrorPrint("50002", "operation failed", "")
|
||||
ErrClockRewind = NewErrorPrint("50003", "snowflake clock rewind", "")
|
||||
ErrAvailable = NewErrorPrint("50301", "unavailable", "")
|
||||
ErrTimeout = NewErrorPrint("50401", "timeout", "")
|
||||
)
|
||||
|
||||
// ErrInternal is an alias of ErrInner.
|
||||
|
||||
@ -31,7 +31,7 @@ func init() {
|
||||
},
|
||||
KeySMSEmailBodyLogin: {
|
||||
ZhCN: "您的登录验证码是 {code},5 分钟内有效。如非本人操作请忽略。",
|
||||
En: "Your login code is {code}. It expires in 5 minutes. Ignore this if you did not request it.",
|
||||
En: "Your login code is {code}. It expires in 5 minutes. Ignore this if you did not request it.",
|
||||
ZhTW: "您的登入驗證碼是 {code},5 分鐘內有效。如非本人操作請忽略。",
|
||||
},
|
||||
KeySMSEmailSubjectDelete: {
|
||||
@ -39,7 +39,7 @@ func init() {
|
||||
},
|
||||
KeySMSEmailBodyDelete: {
|
||||
ZhCN: "您的注销验证码是 {code},5 分钟内有效。提交后将永久删除账号。",
|
||||
En: "Your account deletion code is {code}. It expires in 5 minutes. Submitting will permanently delete your account.",
|
||||
En: "Your account deletion code is {code}. It expires in 5 minutes. Submitting will permanently delete your account.",
|
||||
ZhTW: "您的註銷驗證碼是 {code},5 分鐘內有效。提交後將永久刪除帳號。",
|
||||
},
|
||||
"enum.verification_purpose.login": {
|
||||
|
||||
@ -170,18 +170,18 @@ const (
|
||||
KeySquareCommentInvalid = "validate.square.comment_invalid"
|
||||
KeySquareReplyInvalid = "validate.square.reply_invalid"
|
||||
|
||||
KeyFieldVerificationTarget = "field.verification_target"
|
||||
KeySMSLanguageRequired = "validate.sms.language_required"
|
||||
KeySMSTargetRequired = "validate.sms.target_required"
|
||||
KeySMSTargetInvalid = "validate.sms.target_invalid"
|
||||
KeyFieldVerificationPurpose = "field.verification_purpose"
|
||||
KeySMSPurposeRequired = "validate.sms.purpose_required"
|
||||
KeySMSPurposeInvalid = "validate.sms.purpose_invalid"
|
||||
KeyFieldVerificationTarget = "field.verification_target"
|
||||
KeySMSLanguageRequired = "validate.sms.language_required"
|
||||
KeySMSTargetRequired = "validate.sms.target_required"
|
||||
KeySMSTargetInvalid = "validate.sms.target_invalid"
|
||||
KeyFieldVerificationPurpose = "field.verification_purpose"
|
||||
KeySMSPurposeRequired = "validate.sms.purpose_required"
|
||||
KeySMSPurposeInvalid = "validate.sms.purpose_invalid"
|
||||
KeySMSDeletePhoneUnsupported = "validate.sms.delete_phone_unsupported"
|
||||
KeySMSEmailSubjectLogin = "sms.email.subject_login"
|
||||
KeySMSEmailBodyLogin = "sms.email.body_login"
|
||||
KeySMSEmailSubjectDelete = "sms.email.subject_delete"
|
||||
KeySMSEmailBodyDelete = "sms.email.body_delete"
|
||||
KeySMSEmailSubjectLogin = "sms.email.subject_login"
|
||||
KeySMSEmailBodyLogin = "sms.email.body_login"
|
||||
KeySMSEmailSubjectDelete = "sms.email.subject_delete"
|
||||
KeySMSEmailBodyDelete = "sms.email.body_delete"
|
||||
|
||||
NotifyKickReplaced = "notify.kick.replaced"
|
||||
NotifyKickLogout = "notify.kick.logout"
|
||||
|
||||
@ -23,7 +23,7 @@ func TestAdminImStatsRequiresJWT(t *testing.T) {
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status %d body %s", w.Code, w.Body.String())
|
||||
}
|
||||
var env adminhandler.ApiResponse
|
||||
var env adminhandler.APIResponse
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -50,7 +50,7 @@ func TestAdminImStatsWithToken(t *testing.T) {
|
||||
// nil db → 500 with admin: mysql not ready message in Fail body
|
||||
t.Fatalf("status %d body %s", w.Code, w.Body.String())
|
||||
}
|
||||
var env adminhandler.ApiResponse
|
||||
var env adminhandler.APIResponse
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -76,7 +76,7 @@ func TestAdminPopAlias(t *testing.T) {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d %s", w.Code, w.Body.String())
|
||||
}
|
||||
var env adminhandler.ApiResponse
|
||||
var env adminhandler.APIResponse
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ type ginEngineBundle struct {
|
||||
engine http.Handler
|
||||
}
|
||||
|
||||
func loginUser(t *testing.T, h http.Handler, email string, device uint64) authentity.IssuedToken {
|
||||
func loginUser(t *testing.T, h http.Handler, email string, _ uint64) authentity.IssuedToken {
|
||||
t.Helper()
|
||||
body := authlogictest.LoginBodyJSONNamed(email, "any", "phone")
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/auth/login", strings.NewReader(body))
|
||||
|
||||
@ -136,7 +136,8 @@ func (h *Handler) ExportGroupZip(c *gin.Context) {
|
||||
csvBuf := &bytes.Buffer{}
|
||||
w := csv.NewWriter(csvBuf)
|
||||
_ = w.Write([]string{"userId", "email", "nickname", "role"})
|
||||
for _, m := range g.Members {
|
||||
for i := range g.Members {
|
||||
m := &g.Members[i]
|
||||
_ = w.Write([]string{
|
||||
strconv.FormatInt(m.UserID, 10), m.Email, m.Nickname, m.Role,
|
||||
})
|
||||
@ -212,7 +213,7 @@ func (h *Handler) OpsCatalog(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) ApiDoc(c *gin.Context) {
|
||||
func (h *Handler) APIDoc(c *gin.Context) {
|
||||
OK(c, gin.H{
|
||||
"title": "Spark IM V3 Admin API",
|
||||
"paths": []gin.H{
|
||||
|
||||
@ -6,24 +6,24 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ApiResponse matches Java com.aliyun.control.dto.ApiResponse for popadmin.js.
|
||||
type ApiResponse struct {
|
||||
// APIResponse matches Java com.aliyun.control.dto.ApiResponse for popadmin.js.
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func OK(c *gin.Context, data any) {
|
||||
c.JSON(http.StatusOK, ApiResponse{Success: true, Message: "ok", Data: data})
|
||||
c.JSON(http.StatusOK, APIResponse{Success: true, Message: "ok", Data: data})
|
||||
}
|
||||
|
||||
func OKMsg(c *gin.Context, msg string, data any) {
|
||||
c.JSON(http.StatusOK, ApiResponse{Success: true, Message: msg, Data: data})
|
||||
c.JSON(http.StatusOK, APIResponse{Success: true, Message: msg, Data: data})
|
||||
}
|
||||
|
||||
func Fail(c *gin.Context, status int, msg string) {
|
||||
if status <= 0 {
|
||||
status = http.StatusBadRequest
|
||||
}
|
||||
c.JSON(status, ApiResponse{Success: false, Message: msg, Data: nil})
|
||||
c.JSON(status, APIResponse{Success: false, Message: msg, Data: nil})
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
smslogic "spark-server/internal/module/sms/logic"
|
||||
smsentity "spark-server/internal/module/sms/entity"
|
||||
smslogic "spark-server/internal/module/sms/logic"
|
||||
"spark-server/internal/pkg/errs"
|
||||
"spark-server/internal/pkg/i18n"
|
||||
"spark-server/internal/pkg/reqctx"
|
||||
|
||||
@ -19,8 +19,8 @@ import (
|
||||
metahandler "spark-server/internal/transport/http/handler/meta"
|
||||
momenthandler "spark-server/internal/transport/http/handler/moment"
|
||||
musichandler "spark-server/internal/transport/http/handler/music"
|
||||
squarehandler "spark-server/internal/transport/http/handler/square"
|
||||
smshandler "spark-server/internal/transport/http/handler/sms"
|
||||
squarehandler "spark-server/internal/transport/http/handler/square"
|
||||
userhandler "spark-server/internal/transport/http/handler/user"
|
||||
"spark-server/internal/transport/http/respond"
|
||||
)
|
||||
|
||||
@ -22,7 +22,7 @@ func registerAdminRoutes(r *gin.Engine, deps *EngineDeps) {
|
||||
ug := r.Group("/api/admin")
|
||||
ug.Use(auth)
|
||||
{
|
||||
ug.GET("/api-doc", h.ApiDoc)
|
||||
ug.GET("/api-doc", h.APIDoc)
|
||||
ug.GET("/users", h.ListUsers)
|
||||
ug.GET("/users/by-email", h.UserByEmail)
|
||||
ug.GET("/users/by-nickname", h.UserByNickname)
|
||||
@ -49,7 +49,7 @@ func registerAdminRoutes(r *gin.Engine, deps *EngineDeps) {
|
||||
}
|
||||
|
||||
func registerSparkAdmin(g *gin.RouterGroup, h *adminhandler.Handler) {
|
||||
g.GET("/api-doc", h.ApiDoc)
|
||||
g.GET("/api-doc", h.APIDoc)
|
||||
g.GET("/ops-catalog", h.OpsCatalog)
|
||||
|
||||
g.GET("/im/stats", h.ImStats)
|
||||
|
||||
@ -20,8 +20,8 @@ import (
|
||||
msglogic "spark-server/internal/module/message/logic"
|
||||
momentlogic "spark-server/internal/module/moment/logic"
|
||||
musiclogic "spark-server/internal/module/music/logic"
|
||||
squarelogic "spark-server/internal/module/square/logic"
|
||||
smslogic "spark-server/internal/module/sms/logic"
|
||||
squarelogic "spark-server/internal/module/square/logic"
|
||||
synclogic "spark-server/internal/module/sync/logic"
|
||||
uploadlogic "spark-server/internal/module/upload/logic"
|
||||
userlogic "spark-server/internal/module/user/logic"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user