feat(server): admin 批次列表/作废改指 codes 库新表,清理被取代的旧 store 方法(#codes-lib)
This commit is contained in:
@@ -31,7 +31,7 @@ func (s *Store) ListBatches(ctx context.Context, limit, offset int) ([]BatchInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var total int
|
var total int
|
||||||
if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM code_batches`).Scan(&total); err != nil {
|
if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM codes_batches`).Scan(&total); err != nil {
|
||||||
return nil, 0, fmt.Errorf("store.ListBatches count: %w", err)
|
return nil, 0, fmt.Errorf("store.ListBatches count: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ func (s *Store) ListBatches(ctx context.Context, limit, offset int) ([]BatchInfo
|
|||||||
SUM(c.status = 'redeemed'),
|
SUM(c.status = 'redeemed'),
|
||||||
SUM(c.status = 'void'),
|
SUM(c.status = 'void'),
|
||||||
SUM(c.status = 'unused')
|
SUM(c.status = 'unused')
|
||||||
FROM code_batches b
|
FROM codes_batches b
|
||||||
LEFT JOIN codes c ON c.batch_id = b.id
|
LEFT JOIN codes c ON c.batch_id = b.id
|
||||||
GROUP BY b.id, b.channel, b.created_by, b.note, b.created_at
|
GROUP BY b.id, b.channel, b.created_by, b.note, b.created_at
|
||||||
ORDER BY b.id DESC
|
ORDER BY b.id DESC
|
||||||
@@ -73,7 +73,7 @@ func (s *Store) ListBatches(ctx context.Context, limit, offset int) ([]BatchInfo
|
|||||||
// number of codes voided. Already-redeemed codes are left untouched.
|
// number of codes voided. Already-redeemed codes are left untouched.
|
||||||
func (s *Store) VoidBatch(ctx context.Context, batchID int64) (int64, error) {
|
func (s *Store) VoidBatch(ctx context.Context, batchID int64) (int64, error) {
|
||||||
res, err := s.db.ExecContext(ctx,
|
res, err := s.db.ExecContext(ctx,
|
||||||
`UPDATE codes SET status = 'void' WHERE batch_id = ? AND status = 'unused'`, batchID)
|
`UPDATE codes SET status = 'void', void_reason = 'batch_void' WHERE batch_id = ? AND status = 'unused'`, batchID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("store.VoidBatch: %w", err)
|
return 0, fmt.Errorf("store.VoidBatch: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package codes_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wangjia/pangolin/server/internal/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListBatchesAndVoidBatchOnLibTables(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
db := openMigratedSQLite(t)
|
||||||
|
store := codes.NewStore(db)
|
||||||
|
svc := codes.NewService(store, nil, 5, time.Hour)
|
||||||
|
|
||||||
|
res, err := svc.CreateBatch(ctx, codes.BatchRequest{
|
||||||
|
PlanCode: codes.PlanPro, DurationDays: 30, Count: 3,
|
||||||
|
Channel: codes.ChannelManual, Note: "n", CreatedBy: "admin:1",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("mint: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
infos, total, err := store.ListBatches(ctx, 50, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListBatches: %v", err)
|
||||||
|
}
|
||||||
|
if total != 1 || len(infos) != 1 {
|
||||||
|
t.Fatalf("total=%d len=%d", total, len(infos))
|
||||||
|
}
|
||||||
|
bi := infos[0]
|
||||||
|
if bi.ID != res.BatchID || bi.Channel != codes.ChannelManual || bi.CreatedBy != "admin:1" ||
|
||||||
|
bi.Note != "n" || bi.Total != 3 || bi.Unused != 3 || bi.Redeemed != 0 || bi.Void != 0 {
|
||||||
|
t.Fatalf("BatchInfo = %+v", bi)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := store.VoidBatch(ctx, res.BatchID)
|
||||||
|
if err != nil || n != 3 {
|
||||||
|
t.Fatalf("VoidBatch = %d, %v", n, err)
|
||||||
|
}
|
||||||
|
infos, _, err = store.ListBatches(ctx, 50, 0)
|
||||||
|
if err != nil || infos[0].Void != 3 || infos[0].Unused != 0 {
|
||||||
|
t.Fatalf("after void: %+v (err=%v)", infos[0], err)
|
||||||
|
}
|
||||||
|
// 二次 void:0 行(只动 unused)。
|
||||||
|
if n, err := store.VoidBatch(ctx, res.BatchID); err != nil || n != 0 {
|
||||||
|
t.Fatalf("re-void = %d, %v", n, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,21 +43,12 @@ const (
|
|||||||
|
|
||||||
// SubscriptionRow mirrors the `subscriptions` DB row.
|
// SubscriptionRow mirrors the `subscriptions` DB row.
|
||||||
type SubscriptionRow struct {
|
type SubscriptionRow struct {
|
||||||
ID int64
|
|
||||||
UserID int64
|
|
||||||
PlanID int64
|
|
||||||
PlanCode PlanCode
|
|
||||||
ExpiresAt time.Time
|
|
||||||
Source string
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchRow mirrors the `code_batches` DB row.
|
|
||||||
type BatchRow struct {
|
|
||||||
ID int64
|
ID int64
|
||||||
Channel BatchChannel
|
UserID int64
|
||||||
CreatedBy string
|
PlanID int64
|
||||||
Note sql.NullString
|
PlanCode PlanCode
|
||||||
CreatedAt time.Time
|
ExpiresAt time.Time
|
||||||
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store wraps a *sql.DB and exposes all database operations needed by the
|
// Store wraps a *sql.DB and exposes all database operations needed by the
|
||||||
@@ -248,4 +239,3 @@ func (s *Store) MintOne(ctx context.Context, codeHash string, ent libcodes.Entit
|
|||||||
func (s *Store) BeginTx(ctx context.Context) (*sql.Tx, error) {
|
func (s *Store) BeginTx(ctx context.Context) (*sql.Tx, error) {
|
||||||
return s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
return s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user