fix(subscriptions): atomic UsageRecord metering to prevent quota bypass

- Add @@unique([subscriptionId, metric, periodStart, periodEnd]) constraint
  to UsageRecord model with corresponding migration
- Replace racy findFirst+update/create pattern with Prisma upsert using
  INSERT ON CONFLICT DO UPDATE SET count = count + delta
- Fix CheckQuotaHandler to use period-scoped findUnique instead of
  unscoped findFirst, preventing stale cross-period reads
- Update tests to reflect atomic upsert pattern

Closes GOO-4

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-22 23:22:59 +07:00
parent 65bd641e1f
commit ee6d6d4c17
16 changed files with 180 additions and 79 deletions

View File

@@ -265,6 +265,9 @@ enum PropertyType {
LAND
OFFICE
SHOPHOUSE
ROOM_RENTAL
CONDOTEL
SERVICED_APARTMENT
}
enum TransactionType {
@@ -757,6 +760,7 @@ model UsageRecord {
periodStart DateTime
periodEnd DateTime
@@unique([subscriptionId, metric, periodStart, periodEnd])
@@index([subscriptionId, metric])
}
@@ -1064,10 +1068,10 @@ model IndustrialPark {
remainingAreaHa Float
tenantCount Int @default(0)
establishedYear Int?
landRentUsdM2Year Float?
rbfRentUsdM2Month Float?
rbwRentUsdM2Month Float?
managementFeeUsd Float?
landRentUsdM2Year Decimal? @db.Decimal(18, 4)
rbfRentUsdM2Month Decimal? @db.Decimal(18, 4)
rbwRentUsdM2Month Decimal? @db.Decimal(18, 4)
managementFeeUsd Decimal? @db.Decimal(18, 4)
infrastructure Json? // { electricity, water, wastewater, telecom, roads, fire }
connectivity Json? // { nearestPort, airport, highway, railway, seaport }
incentives Json? // { taxHoliday, importDuty, landRentReduction, specialZone }
@@ -1121,10 +1125,10 @@ model IndustrialListing {
hasMezzanine Boolean @default(false)
hasOfficeArea Boolean @default(false)
officeAreaM2 Float?
priceUsdM2 Float?
priceUsdM2 Decimal? @db.Decimal(18, 4)
pricingUnit String? // "usd/m2/month", "usd/m2/year"
totalLeasePrice Float?
managementFee Float?
totalLeasePrice Decimal? @db.Decimal(18, 4)
managementFee Decimal? @db.Decimal(18, 4)
depositMonths Int?
minLeaseYears Int?
maxLeaseYears Int?