| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- package repository
- import (
- "context"
- "time"
- dbent "github.com/Wei-Shaw/sub2api/ent"
- "github.com/Wei-Shaw/sub2api/ent/group"
- "github.com/Wei-Shaw/sub2api/ent/predicate"
- "github.com/Wei-Shaw/sub2api/ent/schema/mixins"
- "github.com/Wei-Shaw/sub2api/ent/user"
- "github.com/Wei-Shaw/sub2api/ent/usersubscription"
- "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
- "github.com/Wei-Shaw/sub2api/internal/service"
- )
- type userSubscriptionRepository struct {
- client *dbent.Client
- }
- func NewUserSubscriptionRepository(client *dbent.Client) service.UserSubscriptionRepository {
- return &userSubscriptionRepository{client: client}
- }
- func (r *userSubscriptionRepository) Create(ctx context.Context, sub *service.UserSubscription) error {
- if sub == nil {
- return service.ErrSubscriptionNilInput
- }
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "", 0, sub.UserID, func(next context.Context) error { return r.Create(next, sub) })
- }
- client := clientFromContext(ctx, r.client)
- builder := client.UserSubscription.Create().
- SetUserID(sub.UserID).
- SetGroupID(sub.GroupID).
- SetExpiresAt(sub.ExpiresAt).
- SetNillableDailyWindowStart(sub.DailyWindowStart).
- SetNillableWeeklyWindowStart(sub.WeeklyWindowStart).
- SetNillableMonthlyWindowStart(sub.MonthlyWindowStart).
- SetDailyUsageUsd(sub.DailyUsageUSD).
- SetWeeklyUsageUsd(sub.WeeklyUsageUSD).
- SetMonthlyUsageUsd(sub.MonthlyUsageUSD).
- SetNillableAssignedBy(sub.AssignedBy)
- if sub.StartsAt.IsZero() {
- builder.SetStartsAt(time.Now())
- } else {
- builder.SetStartsAt(sub.StartsAt)
- }
- if sub.Status != "" {
- builder.SetStatus(sub.Status)
- }
- if !sub.AssignedAt.IsZero() {
- builder.SetAssignedAt(sub.AssignedAt)
- }
- // Keep compatibility with historical behavior: always store notes as a string value.
- builder.SetNotes(sub.Notes)
- created, err := builder.Save(ctx)
- if err == nil {
- applyUserSubscriptionEntityToService(sub, created)
- }
- return translatePersistenceError(err, nil, service.ErrSubscriptionAlreadyExists)
- }
- func (r *userSubscriptionRepository) GetByID(ctx context.Context, id int64) (*service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- m, err := client.UserSubscription.Query().
- Where(usersubscription.IDEQ(id)).
- WithUser().
- WithGroup().
- WithAssignedByUser().
- Only(ctx)
- if err != nil {
- return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- return userSubscriptionEntityToService(m), nil
- }
- func (r *userSubscriptionRepository) GetByIDForUpdate(ctx context.Context, id int64) (*service.UserSubscription, error) {
- if needsManagedResourceGuard(ctx) {
- var result *service.UserSubscription
- err := withManagedResourceWrite(ctx, r.client, "user_subscriptions", id, 0, func(next context.Context) error {
- var err error
- result, err = r.GetByIDForUpdate(next, id)
- return err
- })
- return result, err
- }
- client := clientFromContext(ctx, r.client)
- m, err := client.UserSubscription.Query().
- Where(usersubscription.IDEQ(id)).
- ForUpdate().
- Only(ctx)
- if err != nil {
- return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- return userSubscriptionEntityToService(m), nil
- }
- func (r *userSubscriptionRepository) GetByIDIncludeDeleted(ctx context.Context, id int64) (*service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- queryCtx := mixins.SkipSoftDelete(ctx)
- m, err := client.UserSubscription.Query().
- Where(usersubscription.IDEQ(id)).
- WithUser().
- WithGroup().
- WithAssignedByUser().
- Only(queryCtx)
- if err != nil {
- return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- return userSubscriptionEntityToServicePreserveStatus(m), nil
- }
- func (r *userSubscriptionRepository) GetByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (*service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- m, err := client.UserSubscription.Query().
- Where(usersubscription.UserIDEQ(userID), usersubscription.GroupIDEQ(groupID)).
- WithGroup().
- Only(ctx)
- if err != nil {
- return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- return userSubscriptionEntityToService(m), nil
- }
- func (r *userSubscriptionRepository) GetActiveByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (*service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- m, err := client.UserSubscription.Query().
- Where(
- usersubscription.UserIDEQ(userID),
- usersubscription.GroupIDEQ(groupID),
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtGT(time.Now()),
- ).
- WithGroup().
- Only(ctx)
- if err != nil {
- return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- return userSubscriptionEntityToService(m), nil
- }
- func (r *userSubscriptionRepository) Update(ctx context.Context, sub *service.UserSubscription) error {
- if sub == nil {
- return service.ErrSubscriptionNilInput
- }
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "user_subscriptions", sub.ID, sub.UserID, func(next context.Context) error { return r.Update(next, sub) })
- }
- client := clientFromContext(ctx, r.client)
- builder := client.UserSubscription.UpdateOneID(sub.ID).
- SetUserID(sub.UserID).
- SetGroupID(sub.GroupID).
- SetStartsAt(sub.StartsAt).
- SetExpiresAt(sub.ExpiresAt).
- SetStatus(sub.Status).
- SetNillableDailyWindowStart(sub.DailyWindowStart).
- SetNillableWeeklyWindowStart(sub.WeeklyWindowStart).
- SetNillableMonthlyWindowStart(sub.MonthlyWindowStart).
- SetDailyUsageUsd(sub.DailyUsageUSD).
- SetWeeklyUsageUsd(sub.WeeklyUsageUSD).
- SetMonthlyUsageUsd(sub.MonthlyUsageUSD).
- SetNillableAssignedBy(sub.AssignedBy).
- SetAssignedAt(sub.AssignedAt).
- SetNotes(sub.Notes)
- updated, err := builder.Save(ctx)
- if err == nil {
- applyUserSubscriptionEntityToService(sub, updated)
- return nil
- }
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, service.ErrSubscriptionAlreadyExists)
- }
- func (r *userSubscriptionRepository) Delete(ctx context.Context, id int64) error {
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "user_subscriptions", id, 0, func(next context.Context) error { return r.Delete(next, id) })
- }
- // Match GORM semantics: deleting a missing row is not an error.
- client := clientFromContext(ctx, r.client)
- _, err := client.UserSubscription.Delete().Where(usersubscription.IDEQ(id)).Exec(ctx)
- return err
- }
- func (r *userSubscriptionRepository) Restore(ctx context.Context, subscriptionID int64, restoredStatus string) (*service.UserSubscription, error) {
- if needsManagedResourceGuard(ctx) {
- var result *service.UserSubscription
- err := withManagedResourceWrite(ctx, r.client, "user_subscriptions", subscriptionID, 0, func(next context.Context) error {
- var err error
- result, err = r.Restore(next, subscriptionID, restoredStatus)
- return err
- })
- return result, err
- }
- client := clientFromContext(ctx, r.client)
- queryCtx := mixins.SkipSoftDelete(ctx)
- _, err := client.UserSubscription.UpdateOneID(subscriptionID).
- SetStatus(restoredStatus).
- ClearDeletedAt().
- SetUpdatedAt(time.Now()).
- Save(queryCtx)
- if err != nil {
- return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, service.ErrSubscriptionRestoreConflict)
- }
- return r.GetByID(ctx, subscriptionID)
- }
- func (r *userSubscriptionRepository) ListByUserID(ctx context.Context, userID int64) ([]service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- subs, err := client.UserSubscription.Query().
- Where(usersubscription.UserIDEQ(userID)).
- WithGroup().
- Order(dbent.Desc(usersubscription.FieldCreatedAt)).
- All(ctx)
- if err != nil {
- return nil, err
- }
- return userSubscriptionEntitiesToService(subs), nil
- }
- func (r *userSubscriptionRepository) ListActiveByUserID(ctx context.Context, userID int64) ([]service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- subs, err := client.UserSubscription.Query().
- Where(
- usersubscription.UserIDEQ(userID),
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtGT(time.Now()),
- ).
- WithGroup().
- Order(dbent.Desc(usersubscription.FieldCreatedAt)).
- All(ctx)
- if err != nil {
- return nil, err
- }
- return userSubscriptionEntitiesToService(subs), nil
- }
- func (r *userSubscriptionRepository) ListByGroupID(ctx context.Context, groupID int64, params pagination.PaginationParams) ([]service.UserSubscription, *pagination.PaginationResult, error) {
- client := clientFromContext(ctx, r.client)
- q := client.UserSubscription.Query().Where(usersubscription.GroupIDEQ(groupID))
- total, err := q.Clone().Count(ctx)
- if err != nil {
- return nil, nil, err
- }
- subs, err := q.
- WithUser().
- WithGroup().
- Order(dbent.Desc(usersubscription.FieldCreatedAt)).
- Offset(params.Offset()).
- Limit(params.Limit()).
- All(ctx)
- if err != nil {
- return nil, nil, err
- }
- return userSubscriptionEntitiesToService(subs), paginationResultFromTotal(int64(total), params), nil
- }
- func (r *userSubscriptionRepository) List(ctx context.Context, params pagination.PaginationParams, userID, groupID *int64, status, platform, sortBy, sortOrder string) ([]service.UserSubscription, *pagination.PaginationResult, error) {
- client := clientFromContext(ctx, r.client)
- q := client.UserSubscription.Query()
- includeSoftDeleted := status == "" || status == service.SubscriptionStatusRevoked
- if userID != nil {
- q = q.Where(usersubscription.UserIDEQ(*userID))
- }
- if groupID != nil {
- q = q.Where(usersubscription.GroupIDEQ(*groupID))
- }
- if platform != "" {
- groupPredicates := []predicate.Group{group.PlatformEQ(platform)}
- if includeSoftDeleted {
- groupPredicates = append(groupPredicates, group.DeletedAtIsNil())
- }
- q = q.Where(usersubscription.HasGroupWith(groupPredicates...))
- }
- // Status filtering with real-time expiration check
- now := time.Now()
- switch status {
- case service.SubscriptionStatusActive:
- // Active: status is active AND not yet expired
- q = q.Where(
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtGT(now),
- )
- case service.SubscriptionStatusExpired:
- // Expired: status is expired OR (status is active but already expired)
- q = q.Where(
- usersubscription.Or(
- usersubscription.StatusEQ(service.SubscriptionStatusExpired),
- usersubscription.And(
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtLTE(now),
- ),
- ),
- )
- case service.SubscriptionStatusRevoked:
- // Revoked is a DTO/API display state backed by user_subscriptions.deleted_at.
- q = q.Where(usersubscription.DeletedAtNotNil())
- case "":
- // No filter. Use SkipSoftDelete below so admin "all status" includes revoked history.
- default:
- // Other persisted status.
- q = q.Where(usersubscription.StatusEQ(status))
- }
- queryCtx := ctx
- if includeSoftDeleted {
- queryCtx = mixins.SkipSoftDelete(ctx)
- }
- total, err := q.Clone().Count(queryCtx)
- if err != nil {
- return nil, nil, err
- }
- if !includeSoftDeleted {
- q = q.WithUser().WithGroup().WithAssignedByUser()
- }
- // Determine sort field
- var field string
- switch sortBy {
- case "expires_at":
- field = usersubscription.FieldExpiresAt
- case "status":
- field = usersubscription.FieldStatus
- default:
- field = usersubscription.FieldCreatedAt
- }
- // Determine sort order (default: desc)
- if sortOrder == "asc" && sortBy != "" {
- q = q.Order(dbent.Asc(field))
- } else {
- q = q.Order(dbent.Desc(field))
- }
- subs, err := q.
- Offset(params.Offset()).
- Limit(params.Limit()).
- All(queryCtx)
- if err != nil {
- return nil, nil, err
- }
- result := userSubscriptionEntitiesToService(subs)
- if includeSoftDeleted {
- if err := r.attachUserSubscriptionRelations(ctx, result); err != nil {
- return nil, nil, err
- }
- }
- return result, paginationResultFromTotal(int64(total), params), nil
- }
- func (r *userSubscriptionRepository) ExistsByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (bool, error) {
- client := clientFromContext(ctx, r.client)
- return client.UserSubscription.Query().
- Where(usersubscription.UserIDEQ(userID), usersubscription.GroupIDEQ(groupID)).
- Exist(ctx)
- }
- func (r *userSubscriptionRepository) ExistsActiveByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (bool, error) {
- return r.ExistsByUserIDAndGroupID(ctx, userID, groupID)
- }
- func (r *userSubscriptionRepository) ExtendExpiry(ctx context.Context, subscriptionID int64, newExpiresAt time.Time) error {
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "user_subscriptions", subscriptionID, 0, func(next context.Context) error { return r.ExtendExpiry(next, subscriptionID, newExpiresAt) })
- }
- client := clientFromContext(ctx, r.client)
- _, err := client.UserSubscription.UpdateOneID(subscriptionID).
- SetExpiresAt(newExpiresAt).
- Save(ctx)
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- func (r *userSubscriptionRepository) UpdateStatus(ctx context.Context, subscriptionID int64, status string) error {
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "user_subscriptions", subscriptionID, 0, func(next context.Context) error { return r.UpdateStatus(next, subscriptionID, status) })
- }
- client := clientFromContext(ctx, r.client)
- _, err := client.UserSubscription.UpdateOneID(subscriptionID).
- SetStatus(status).
- Save(ctx)
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- func (r *userSubscriptionRepository) UpdateNotes(ctx context.Context, subscriptionID int64, notes string) error {
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "user_subscriptions", subscriptionID, 0, func(next context.Context) error { return r.UpdateNotes(next, subscriptionID, notes) })
- }
- client := clientFromContext(ctx, r.client)
- _, err := client.UserSubscription.UpdateOneID(subscriptionID).
- SetNotes(notes).
- Save(ctx)
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- func (r *userSubscriptionRepository) ActivateWindows(ctx context.Context, id int64, dailyStart, periodicStart time.Time) error {
- client := clientFromContext(ctx, r.client)
- n, err := client.UserSubscription.Update().
- Where(
- usersubscription.IDEQ(id),
- usersubscription.DailyWindowStartIsNil(),
- usersubscription.WeeklyWindowStartIsNil(),
- usersubscription.MonthlyWindowStartIsNil(),
- ).
- SetDailyWindowStart(dailyStart).
- SetWeeklyWindowStart(periodicStart).
- SetMonthlyWindowStart(periodicStart).
- Save(ctx)
- return r.translateConditionalWindowReset(ctx, client, id, n, err)
- }
- func (r *userSubscriptionRepository) ResetUsageWindows(ctx context.Context, id int64, resetDaily, resetWeekly, resetMonthly bool, dailyStart, periodicStart time.Time) error {
- if needsManagedResourceGuard(ctx) {
- return withManagedResourceWrite(ctx, r.client, "user_subscriptions", id, 0, func(next context.Context) error {
- return r.ResetUsageWindows(next, id, resetDaily, resetWeekly, resetMonthly, dailyStart, periodicStart)
- })
- }
- client := clientFromContext(ctx, r.client)
- update := client.UserSubscription.UpdateOneID(id)
- if resetDaily {
- update.SetDailyUsageUsd(0).SetDailyWindowStart(dailyStart)
- }
- if resetWeekly {
- update.SetWeeklyUsageUsd(0).SetWeeklyWindowStart(periodicStart)
- }
- if resetMonthly {
- update.SetMonthlyUsageUsd(0).SetMonthlyWindowStart(periodicStart)
- }
- _, err := update.Save(ctx)
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- func (r *userSubscriptionRepository) ResetDailyUsage(ctx context.Context, id int64, expectedWindowStart *time.Time, newWindowStart time.Time) error {
- client := clientFromContext(ctx, r.client)
- query := client.UserSubscription.Update().Where(usersubscription.IDEQ(id))
- if expectedWindowStart == nil {
- query = query.Where(usersubscription.DailyWindowStartIsNil())
- } else {
- query = query.Where(usersubscription.DailyWindowStartEQ(*expectedWindowStart))
- }
- n, err := query.
- SetDailyUsageUsd(0).
- SetDailyWindowStart(newWindowStart).
- Save(ctx)
- return r.translateConditionalWindowReset(ctx, client, id, n, err)
- }
- func (r *userSubscriptionRepository) ResetWeeklyUsage(ctx context.Context, id int64, expectedWindowStart *time.Time, newWindowStart time.Time) error {
- client := clientFromContext(ctx, r.client)
- query := client.UserSubscription.Update().Where(usersubscription.IDEQ(id))
- if expectedWindowStart == nil {
- query = query.Where(usersubscription.WeeklyWindowStartIsNil())
- } else {
- query = query.Where(usersubscription.WeeklyWindowStartEQ(*expectedWindowStart))
- }
- n, err := query.
- SetWeeklyUsageUsd(0).
- SetWeeklyWindowStart(newWindowStart).
- Save(ctx)
- return r.translateConditionalWindowReset(ctx, client, id, n, err)
- }
- func (r *userSubscriptionRepository) ResetMonthlyUsage(ctx context.Context, id int64, expectedWindowStart *time.Time, newWindowStart time.Time) error {
- client := clientFromContext(ctx, r.client)
- query := client.UserSubscription.Update().Where(usersubscription.IDEQ(id))
- if expectedWindowStart == nil {
- query = query.Where(usersubscription.MonthlyWindowStartIsNil())
- } else {
- query = query.Where(usersubscription.MonthlyWindowStartEQ(*expectedWindowStart))
- }
- n, err := query.
- SetMonthlyUsageUsd(0).
- SetMonthlyWindowStart(newWindowStart).
- Save(ctx)
- return r.translateConditionalWindowReset(ctx, client, id, n, err)
- }
- func (r *userSubscriptionRepository) translateConditionalWindowReset(ctx context.Context, client *dbent.Client, id int64, affected int, err error) error {
- if err != nil {
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- if affected > 0 {
- return nil
- }
- // A stale reset is an expected no-op: another request already advanced the
- // window. Preserve not-found semantics for callers that target a missing row.
- exists, err := client.UserSubscription.Query().Where(usersubscription.IDEQ(id)).Exist(ctx)
- if err != nil {
- return translatePersistenceError(err, service.ErrSubscriptionNotFound, nil)
- }
- if !exists {
- return service.ErrSubscriptionNotFound
- }
- return nil
- }
- // IncrementUsage 原子性地累加订阅用量。
- // 限额检查已在请求前由 BillingCacheService.CheckBillingEligibility 完成,
- // 此处仅负责记录实际消费,确保消费数据的完整性。
- func (r *userSubscriptionRepository) IncrementUsage(ctx context.Context, id int64, costUSD float64) error {
- const updateSQL = `
- UPDATE user_subscriptions us
- SET
- daily_usage_usd = us.daily_usage_usd + $1,
- weekly_usage_usd = us.weekly_usage_usd + $1,
- monthly_usage_usd = us.monthly_usage_usd + $1,
- updated_at = NOW()
- FROM groups g
- WHERE us.id = $2
- AND us.deleted_at IS NULL
- AND us.group_id = g.id
- AND g.deleted_at IS NULL
- `
- client := clientFromContext(ctx, r.client)
- result, err := client.ExecContext(ctx, updateSQL, costUSD, id)
- if err != nil {
- return err
- }
- affected, err := result.RowsAffected()
- if err != nil {
- return err
- }
- if affected > 0 {
- return nil
- }
- // affected == 0:订阅不存在或已删除
- return service.ErrSubscriptionNotFound
- }
- func (r *userSubscriptionRepository) BatchUpdateExpiredStatus(ctx context.Context) (int64, error) {
- client := clientFromContext(ctx, r.client)
- n, err := client.UserSubscription.Update().
- Where(
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtLTE(time.Now()),
- ).
- SetStatus(service.SubscriptionStatusExpired).
- Save(ctx)
- return int64(n), err
- }
- // Extra repository helpers (currently used only by integration tests).
- func (r *userSubscriptionRepository) ListExpired(ctx context.Context) ([]service.UserSubscription, error) {
- client := clientFromContext(ctx, r.client)
- subs, err := client.UserSubscription.Query().
- Where(
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtLTE(time.Now()),
- ).
- All(ctx)
- if err != nil {
- return nil, err
- }
- return userSubscriptionEntitiesToService(subs), nil
- }
- func (r *userSubscriptionRepository) CountByGroupID(ctx context.Context, groupID int64) (int64, error) {
- client := clientFromContext(ctx, r.client)
- count, err := client.UserSubscription.Query().Where(usersubscription.GroupIDEQ(groupID)).Count(ctx)
- return int64(count), err
- }
- func (r *userSubscriptionRepository) CountActiveByGroupID(ctx context.Context, groupID int64) (int64, error) {
- client := clientFromContext(ctx, r.client)
- count, err := client.UserSubscription.Query().
- Where(
- usersubscription.GroupIDEQ(groupID),
- usersubscription.StatusEQ(service.SubscriptionStatusActive),
- usersubscription.ExpiresAtGT(time.Now()),
- ).
- Count(ctx)
- return int64(count), err
- }
- func (r *userSubscriptionRepository) DeleteByGroupID(ctx context.Context, groupID int64) (int64, error) {
- client := clientFromContext(ctx, r.client)
- n, err := client.UserSubscription.Delete().Where(usersubscription.GroupIDEQ(groupID)).Exec(ctx)
- return int64(n), err
- }
- func (r *userSubscriptionRepository) attachUserSubscriptionRelations(ctx context.Context, subs []service.UserSubscription) error {
- if len(subs) == 0 {
- return nil
- }
- userIDs := make([]int64, 0, len(subs))
- groupIDs := make([]int64, 0, len(subs))
- assignedByIDs := make([]int64, 0, len(subs))
- for i := range subs {
- userIDs = append(userIDs, subs[i].UserID)
- groupIDs = append(groupIDs, subs[i].GroupID)
- if subs[i].AssignedBy != nil {
- assignedByIDs = append(assignedByIDs, *subs[i].AssignedBy)
- }
- }
- client := clientFromContext(ctx, r.client)
- users, err := client.User.Query().Where(user.IDIn(uniqueInt64s(userIDs)...)).All(ctx)
- if err != nil {
- return err
- }
- userByID := make(map[int64]*service.User, len(users))
- for _, u := range users {
- userByID[u.ID] = userEntityToService(u)
- }
- groups, err := client.Group.Query().Where(group.IDIn(uniqueInt64s(groupIDs)...)).All(ctx)
- if err != nil {
- return err
- }
- groupByID := make(map[int64]*service.Group, len(groups))
- for _, g := range groups {
- groupByID[g.ID] = groupEntityToService(g)
- }
- assignedByID := map[int64]*service.User{}
- if len(assignedByIDs) > 0 {
- assignedUsers, err := client.User.Query().Where(user.IDIn(uniqueInt64s(assignedByIDs)...)).All(ctx)
- if err != nil {
- return err
- }
- assignedByID = make(map[int64]*service.User, len(assignedUsers))
- for _, u := range assignedUsers {
- assignedByID[u.ID] = userEntityToService(u)
- }
- }
- for i := range subs {
- subs[i].User = userByID[subs[i].UserID]
- subs[i].Group = groupByID[subs[i].GroupID]
- if subs[i].AssignedBy != nil {
- subs[i].AssignedByUser = assignedByID[*subs[i].AssignedBy]
- }
- }
- return nil
- }
- func uniqueInt64s(values []int64) []int64 {
- seen := make(map[int64]struct{}, len(values))
- out := make([]int64, 0, len(values))
- for _, v := range values {
- if _, ok := seen[v]; ok {
- continue
- }
- seen[v] = struct{}{}
- out = append(out, v)
- }
- return out
- }
- func userSubscriptionEntityToService(m *dbent.UserSubscription) *service.UserSubscription {
- return userSubscriptionEntityToServiceWithStatusMapping(m, true)
- }
- func userSubscriptionEntityToServicePreserveStatus(m *dbent.UserSubscription) *service.UserSubscription {
- return userSubscriptionEntityToServiceWithStatusMapping(m, false)
- }
- func userSubscriptionEntityToServiceWithStatusMapping(m *dbent.UserSubscription, mapDeletedToRevoked bool) *service.UserSubscription {
- if m == nil {
- return nil
- }
- status := m.Status
- if mapDeletedToRevoked && m.DeletedAt != nil {
- status = service.SubscriptionStatusRevoked
- }
- out := &service.UserSubscription{
- ID: m.ID,
- UserID: m.UserID,
- GroupID: m.GroupID,
- StartsAt: m.StartsAt,
- ExpiresAt: m.ExpiresAt,
- Status: status,
- DailyWindowStart: m.DailyWindowStart,
- WeeklyWindowStart: m.WeeklyWindowStart,
- MonthlyWindowStart: m.MonthlyWindowStart,
- DailyUsageUSD: m.DailyUsageUsd,
- WeeklyUsageUSD: m.WeeklyUsageUsd,
- MonthlyUsageUSD: m.MonthlyUsageUsd,
- AssignedBy: m.AssignedBy,
- AssignedAt: m.AssignedAt,
- Notes: derefString(m.Notes),
- CreatedAt: m.CreatedAt,
- UpdatedAt: m.UpdatedAt,
- DeletedAt: m.DeletedAt,
- }
- if m.Edges.User != nil {
- out.User = userEntityToService(m.Edges.User)
- }
- if m.Edges.Group != nil {
- out.Group = groupEntityToService(m.Edges.Group)
- }
- if m.Edges.AssignedByUser != nil {
- out.AssignedByUser = userEntityToService(m.Edges.AssignedByUser)
- }
- return out
- }
- func userSubscriptionEntitiesToService(models []*dbent.UserSubscription) []service.UserSubscription {
- out := make([]service.UserSubscription, 0, len(models))
- for i := range models {
- if s := userSubscriptionEntityToService(models[i]); s != nil {
- out = append(out, *s)
- }
- }
- return out
- }
- func applyUserSubscriptionEntityToService(dst *service.UserSubscription, src *dbent.UserSubscription) {
- if dst == nil || src == nil {
- return
- }
- dst.ID = src.ID
- dst.CreatedAt = src.CreatedAt
- dst.UpdatedAt = src.UpdatedAt
- }
|