Skip to content

Commit d17aba7

Browse files
AchoArnoldCopilot
andcommitted
fix(billing): use server-computed end_timestamp in Overview and load user within transaction
- Overview section now uses actual end_timestamp from store instead of recomputing via JS arithmetic that diverges from server clamping - Simplified billingPeriod filter to format a single date - createBillingUsageForUser now accepts tx parameter to keep user read within the same CockroachDB transaction snapshot - Removed UserRepository dependency from BillingUsageRepository since user is loaded directly via the transaction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9538336 commit d17aba7

4 files changed

Lines changed: 22 additions & 28 deletions

File tree

api/pkg/di/container.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,6 @@ func (container *Container) BillingUsageRepository() (repository repositories.Bi
843843
container.Logger(),
844844
container.Tracer(),
845845
container.DB(),
846-
container.UserRepository(),
847846
)
848847
}
849848

api/pkg/repositories/gorm_billing_usage_repository.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,21 @@ import (
1616

1717
// gormBillingUsageRepository is responsible for persisting entities.BillingUsage
1818
type gormBillingUsageRepository struct {
19-
logger telemetry.Logger
20-
tracer telemetry.Tracer
21-
db *gorm.DB
22-
userRepository UserRepository
19+
logger telemetry.Logger
20+
tracer telemetry.Tracer
21+
db *gorm.DB
2322
}
2423

2524
// NewGormBillingUsageRepository creates the GORM version of the BillingUsageRepository
2625
func NewGormBillingUsageRepository(
2726
logger telemetry.Logger,
2827
tracer telemetry.Tracer,
2928
db *gorm.DB,
30-
userRepository UserRepository,
3129
) BillingUsageRepository {
3230
return &gormBillingUsageRepository{
33-
logger: logger.WithService(fmt.Sprintf("%T", &gormBillingUsageRepository{})),
34-
tracer: tracer,
35-
db: db,
36-
userRepository: userRepository,
31+
logger: logger.WithService(fmt.Sprintf("%T", &gormBillingUsageRepository{})),
32+
tracer: tracer,
33+
db: db,
3734
}
3835
}
3936

@@ -65,7 +62,7 @@ func (repository *gormBillingUsageRepository) RegisterSentMessage(ctx context.Co
6562
UpdateColumn("sent_messages", gorm.Expr("sent_messages + ?", 1))
6663

6764
if result.Error == nil && result.RowsAffected == 0 {
68-
usage, err := repository.createBillingUsageForUser(ctx, userID, timestamp, 1, 0)
65+
usage, err := repository.createBillingUsageForUser(ctx, tx, userID, timestamp, 1, 0)
6966
if err != nil {
7067
return err
7168
}
@@ -91,7 +88,7 @@ func (repository *gormBillingUsageRepository) RegisterReceivedMessage(ctx contex
9188
UpdateColumn("received_messages", gorm.Expr("received_messages + ?", 1))
9289

9390
if result.Error == nil && result.RowsAffected == 0 {
94-
usage, err := repository.createBillingUsageForUser(ctx, userID, timestamp, 0, 1)
91+
usage, err := repository.createBillingUsageForUser(ctx, tx, userID, timestamp, 0, 1)
9592
if err != nil {
9693
return err
9794
}
@@ -119,7 +116,7 @@ func (repository *gormBillingUsageRepository) GetCurrent(ctx context.Context, us
119116
First(&usage)
120117

121118
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
122-
newUsage, createErr := repository.createBillingUsageForUser(ctx, userID, timestamp, 0, 0)
119+
newUsage, createErr := repository.createBillingUsageForUser(ctx, tx, userID, timestamp, 0, 0)
123120
if createErr != nil {
124121
return createErr
125122
}
@@ -165,9 +162,10 @@ func (repository *gormBillingUsageRepository) GetHistory(ctx context.Context, us
165162
}
166163

167164
// createBillingUsageForUser loads the user to determine anchor day and computes cycle boundaries.
168-
func (repository *gormBillingUsageRepository) createBillingUsageForUser(ctx context.Context, userID entities.UserID, timestamp time.Time, sent uint, received uint) (*entities.BillingUsage, error) {
169-
user, err := repository.userRepository.Load(ctx, userID)
170-
if err != nil {
165+
// It accepts a tx to ensure the user read is part of the same transaction snapshot.
166+
func (repository *gormBillingUsageRepository) createBillingUsageForUser(ctx context.Context, tx *gorm.DB, userID entities.UserID, timestamp time.Time, sent uint, received uint) (*entities.BillingUsage, error) {
167+
user := new(entities.User)
168+
if err := tx.WithContext(ctx).First(user, userID).Error; err != nil {
171169
return nil, stacktrace.Propagate(err, fmt.Sprintf("cannot load user [%s] to compute billing cycle", userID))
172170
}
173171

web/pages/billing/index.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,13 @@
233233
v-if="$store.getters.getBillingUsage"
234234
class="font-weight-bold"
235235
>{{
236-
$store.getters.getBillingUsage.start_timestamp | billingPeriod
236+
$store.getters.getBillingUsage.start_timestamp
237+
| billingPeriodDate
238+
}}
239+
240+
{{
241+
$store.getters.getBillingUsage.end_timestamp
242+
| billingPeriodDate
237243
}}</code
238244
>.
239245
</p>

web/plugins/filters.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,13 @@ Vue.filter('decimal', (value: string): string => {
4545
})
4646

4747
Vue.filter('billingPeriod', (value: string): string => {
48-
const startDate = new Date(value)
48+
const date = new Date(value)
4949
const options: Intl.DateTimeFormatOptions = {
5050
month: 'short',
5151
day: 'numeric',
52-
}
53-
const optionsWithYear: Intl.DateTimeFormatOptions = {
54-
month: 'short',
55-
day: 'numeric',
5652
year: 'numeric',
5753
}
58-
const start = startDate.toLocaleDateString('en-US', options)
59-
const endDate = new Date(startDate)
60-
endDate.setMonth(endDate.getMonth() + 1)
61-
endDate.setDate(endDate.getDate() - 1)
62-
const end = endDate.toLocaleDateString('en-US', optionsWithYear)
63-
return `${start}${end}`
54+
return date.toLocaleDateString('en-US', options)
6455
})
6556

6657
Vue.filter('billingPeriodDate', (value: string): string => {

0 commit comments

Comments
 (0)