- 前端: 所有界面显示、i18n 文本、组件中的品牌名称 - 后端: 服务层、设置默认值、邮件模板、安装向导 - 数据库: 迁移脚本注释 - 保持功能完全一致,仅更改品牌名称 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
240 lines
6.0 KiB
Go
240 lines
6.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
|
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
)
|
|
|
|
type redeemCodeRepository struct {
|
|
client *dbent.Client
|
|
}
|
|
|
|
func NewRedeemCodeRepository(client *dbent.Client) service.RedeemCodeRepository {
|
|
return &redeemCodeRepository{client: client}
|
|
}
|
|
|
|
func (r *redeemCodeRepository) Create(ctx context.Context, code *service.RedeemCode) error {
|
|
created, err := r.client.RedeemCode.Create().
|
|
SetCode(code.Code).
|
|
SetType(code.Type).
|
|
SetValue(code.Value).
|
|
SetStatus(code.Status).
|
|
SetNotes(code.Notes).
|
|
SetValidityDays(code.ValidityDays).
|
|
SetNillableUsedBy(code.UsedBy).
|
|
SetNillableUsedAt(code.UsedAt).
|
|
SetNillableGroupID(code.GroupID).
|
|
Save(ctx)
|
|
if err == nil {
|
|
code.ID = created.ID
|
|
code.CreatedAt = created.CreatedAt
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *redeemCodeRepository) CreateBatch(ctx context.Context, codes []service.RedeemCode) error {
|
|
if len(codes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
builders := make([]*dbent.RedeemCodeCreate, 0, len(codes))
|
|
for i := range codes {
|
|
c := &codes[i]
|
|
b := r.client.RedeemCode.Create().
|
|
SetCode(c.Code).
|
|
SetType(c.Type).
|
|
SetValue(c.Value).
|
|
SetStatus(c.Status).
|
|
SetNotes(c.Notes).
|
|
SetValidityDays(c.ValidityDays).
|
|
SetNillableUsedBy(c.UsedBy).
|
|
SetNillableUsedAt(c.UsedAt).
|
|
SetNillableGroupID(c.GroupID)
|
|
builders = append(builders, b)
|
|
}
|
|
|
|
return r.client.RedeemCode.CreateBulk(builders...).Exec(ctx)
|
|
}
|
|
|
|
func (r *redeemCodeRepository) GetByID(ctx context.Context, id int64) (*service.RedeemCode, error) {
|
|
m, err := r.client.RedeemCode.Query().
|
|
Where(redeemcode.IDEQ(id)).
|
|
Only(ctx)
|
|
if err != nil {
|
|
if dbent.IsNotFound(err) {
|
|
return nil, service.ErrRedeemCodeNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return redeemCodeEntityToService(m), nil
|
|
}
|
|
|
|
func (r *redeemCodeRepository) GetByCode(ctx context.Context, code string) (*service.RedeemCode, error) {
|
|
m, err := r.client.RedeemCode.Query().
|
|
Where(redeemcode.CodeEQ(code)).
|
|
Only(ctx)
|
|
if err != nil {
|
|
if dbent.IsNotFound(err) {
|
|
return nil, service.ErrRedeemCodeNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return redeemCodeEntityToService(m), nil
|
|
}
|
|
|
|
func (r *redeemCodeRepository) Delete(ctx context.Context, id int64) error {
|
|
_, err := r.client.RedeemCode.Delete().Where(redeemcode.IDEQ(id)).Exec(ctx)
|
|
return err
|
|
}
|
|
|
|
func (r *redeemCodeRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.RedeemCode, *pagination.PaginationResult, error) {
|
|
return r.ListWithFilters(ctx, params, "", "", "")
|
|
}
|
|
|
|
func (r *redeemCodeRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, codeType, status, search string) ([]service.RedeemCode, *pagination.PaginationResult, error) {
|
|
q := r.client.RedeemCode.Query()
|
|
|
|
if codeType != "" {
|
|
q = q.Where(redeemcode.TypeEQ(codeType))
|
|
}
|
|
if status != "" {
|
|
q = q.Where(redeemcode.StatusEQ(status))
|
|
}
|
|
if search != "" {
|
|
q = q.Where(redeemcode.CodeContainsFold(search))
|
|
}
|
|
|
|
total, err := q.Count(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
codes, err := q.
|
|
WithUser().
|
|
WithGroup().
|
|
Offset(params.Offset()).
|
|
Limit(params.Limit()).
|
|
Order(dbent.Desc(redeemcode.FieldID)).
|
|
All(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
outCodes := redeemCodeEntitiesToService(codes)
|
|
|
|
return outCodes, paginationResultFromTotal(int64(total), params), nil
|
|
}
|
|
|
|
func (r *redeemCodeRepository) Update(ctx context.Context, code *service.RedeemCode) error {
|
|
up := r.client.RedeemCode.UpdateOneID(code.ID).
|
|
SetCode(code.Code).
|
|
SetType(code.Type).
|
|
SetValue(code.Value).
|
|
SetStatus(code.Status).
|
|
SetNotes(code.Notes).
|
|
SetValidityDays(code.ValidityDays)
|
|
|
|
if code.UsedBy != nil {
|
|
up.SetUsedBy(*code.UsedBy)
|
|
} else {
|
|
up.ClearUsedBy()
|
|
}
|
|
if code.UsedAt != nil {
|
|
up.SetUsedAt(*code.UsedAt)
|
|
} else {
|
|
up.ClearUsedAt()
|
|
}
|
|
if code.GroupID != nil {
|
|
up.SetGroupID(*code.GroupID)
|
|
} else {
|
|
up.ClearGroupID()
|
|
}
|
|
|
|
updated, err := up.Save(ctx)
|
|
if err != nil {
|
|
if dbent.IsNotFound(err) {
|
|
return service.ErrRedeemCodeNotFound
|
|
}
|
|
return err
|
|
}
|
|
code.CreatedAt = updated.CreatedAt
|
|
return nil
|
|
}
|
|
|
|
func (r *redeemCodeRepository) Use(ctx context.Context, id, userID int64) error {
|
|
now := time.Now()
|
|
client := clientFromContext(ctx, r.client)
|
|
affected, err := client.RedeemCode.Update().
|
|
Where(redeemcode.IDEQ(id), redeemcode.StatusEQ(service.StatusUnused)).
|
|
SetStatus(service.StatusUsed).
|
|
SetUsedBy(userID).
|
|
SetUsedAt(now).
|
|
Save(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if affected == 0 {
|
|
return service.ErrRedeemCodeUsed
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *redeemCodeRepository) ListByUser(ctx context.Context, userID int64, limit int) ([]service.RedeemCode, error) {
|
|
if limit <= 0 {
|
|
limit = 10
|
|
}
|
|
|
|
codes, err := r.client.RedeemCode.Query().
|
|
Where(redeemcode.UsedByEQ(userID)).
|
|
WithGroup().
|
|
Order(dbent.Desc(redeemcode.FieldUsedAt)).
|
|
Limit(limit).
|
|
All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return redeemCodeEntitiesToService(codes), nil
|
|
}
|
|
|
|
func redeemCodeEntityToService(m *dbent.RedeemCode) *service.RedeemCode {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
out := &service.RedeemCode{
|
|
ID: m.ID,
|
|
Code: m.Code,
|
|
Type: m.Type,
|
|
Value: m.Value,
|
|
Status: m.Status,
|
|
UsedBy: m.UsedBy,
|
|
UsedAt: m.UsedAt,
|
|
Notes: derefString(m.Notes),
|
|
CreatedAt: m.CreatedAt,
|
|
GroupID: m.GroupID,
|
|
ValidityDays: m.ValidityDays,
|
|
}
|
|
if m.Edges.User != nil {
|
|
out.User = userEntityToService(m.Edges.User)
|
|
}
|
|
if m.Edges.Group != nil {
|
|
out.Group = groupEntityToService(m.Edges.Group)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func redeemCodeEntitiesToService(models []*dbent.RedeemCode) []service.RedeemCode {
|
|
out := make([]service.RedeemCode, 0, len(models))
|
|
for i := range models {
|
|
if s := redeemCodeEntityToService(models[i]); s != nil {
|
|
out = append(out, *s)
|
|
}
|
|
}
|
|
return out
|
|
}
|