Files
sub2api/backend/internal/repository/user_subscription_repo.go
T
李建琦 6d655c9903
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
2026-08-21 18:30:13 +08:00

693 lines
22 KiB
Go

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
}
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) {
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
}
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 {
// 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) {
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 {
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 {
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 {
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 {
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
}