core, plan: correct badge purchase type references

This commit is contained in:
shum
2026-08-27 10:28:26 +00:00
parent 056755fd98
commit f6cbf15963
8 changed files with 45 additions and 22 deletions
+11 -3
View File
@@ -209,9 +209,17 @@ data BadgeAlert = BadgeAlert
-- network.
--
-- @monthsLeft@ and @paidThrough@ come from the purchase's LAST ledger entry: the balance the
-- client believes it holds, and 'Simplex.Chat.Badges.Months.addMonths' applied to it. Both are
-- absent from a purchase with no ledger entry yet. @paidThrough@ is deliberately not the
-- credential's expiry (UX §2.11 forbids presenting one as the other).
-- client believes it holds, and 'Simplex.Chat.Badges.Months.addMonths' applied to it.
--
-- __They differ on a purchase with no ledger entry.__ @paidThrough@ is 'Nothing' — no entry, no
-- date — while @monthsLeft@ is @0@, which is indistinguishable from a balance that has run out.
-- Only @paidThrough@ separates "nothing is known yet" from "nothing is left", so a caller
-- rendering both must branch on @paidThrough@, not on @monthsLeft == 0@. @monthsLeft@ is not a
-- 'Maybe' because every other reader wants a number, and the one distinction it would carry is
-- already carried next to it.
--
-- @paidThrough@ is deliberately not the credential's expiry (UX §2.11 forbids presenting one as
-- the other).
data UserBadge = UserBadge
{ badgePurchaseId :: Int64,
badgeType :: BadgeType,
+3 -3
View File
@@ -1562,9 +1562,9 @@ data ChatErrorType
-- @retryAfter@ is seconds, populated by the service's @rate_limited@. A failure with no
-- service involved (a credential that does not verify) uses 'BSEInternal'.
--
-- The message field is named apart from the @message :: String@ that the rest of this union
-- carries: a record field has one type across a whole data type, and this one is the wire's
-- optional 'Text'.
-- The message field is named apart from the @message :: String@ that 15 other constructors
-- of this union carry: a record field has one type across a whole data type, and this one is
-- the wire's optional 'Text'.
CEBadgeServiceError {badgeError :: BadgeServiceErrorCode, badgeErrorMessage :: Maybe Text, retryAfter :: Maybe Word32}
| CEInternalError {message :: String}
| CEException {message :: String}
+1 -1
View File
@@ -5205,7 +5205,7 @@ presentUserBadgeToContacts :: User -> CM ()
presentUserBadgeToContacts user' = do
cxt <- asks $ mkStoreCxt . config
contacts <- withFastStore' $ \db -> getUserContacts db cxt user'
withChatLock "addUserBadge" $ forM_ contacts $ \ct ->
withChatLock "presentUserBadgeToContacts" $ forM_ contacts $ \ct ->
case contactSendConn_ ct of
Right conn
| not (connIncognito conn) -> do
+9 -6
View File
@@ -100,12 +100,15 @@ import Database.SQLite.Simple.QQ (sql)
-- | The client's own projection of a @badge_purchases@ row: exactly its columns, with the
-- client-only @user_id@ and @purchase_priv_key@ that the service's table does not have.
--
-- This is deliberately NOT 'Simplex.Chat.Badges.Types.BadgePurchase'. That record declares
-- @priceId@, @offerId@ and @credential@, none of which is a column of this table: the price and
-- offer of a purchase live on its @badge_invoices@ rows (one per invoice, so not a function of
-- the purchase), and the credential lives on its @badge_issuances@ rows (one per period). A row
-- type that had to invent three fields to be constructed would report a purchase the database
-- does not hold. 'BadgeService.Store.BadgePurchaseRow' is the same decision on the service side.
-- This is deliberately NOT the single shared purchase record core §3 drafted as
-- @Badges.Types.BadgePurchase@. That draft declared @priceId@, @offerId@ and @credential@, none
-- of which is a column of this table: the price and offer of a purchase live on its
-- @badge_invoices@ rows (one per invoice, so not a function of the purchase), and the credential
-- lives on its @badge_issuances@ rows (one per period). A row type that had to invent three
-- fields to be constructed would report a purchase the database does not hold.
-- 'BadgeService.Store.BadgePurchaseRow' is the same decision on the service side. C2 deleted the
-- draft once both row types existed and split its API half off as
-- 'Simplex.Chat.Badges.Types.UserBadge', which carries no secrets and so may cross the FFI.
data UserBadgePurchase = UserBadgePurchase
{ badgePurchaseId :: Int64,
userId :: UserId,
+2 -1
View File
@@ -4,6 +4,7 @@
module Simplex.Chat.Terminal.Main where
import Control.Applicative ((<|>))
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.STM
import Control.Monad
@@ -37,7 +38,7 @@ simplexChatCLI cfg server_ = do
badgeOptsOverride :: ChatConfig -> ChatOpts -> ChatConfig
badgeOptsOverride cfg ChatOpts {optBadgeServiceAddress, optBadgeWebUrl, optBadgeIssuerKeys} =
cfg
{ badgeServiceAddress = maybe (badgeServiceAddress cfg) Just optBadgeServiceAddress,
{ badgeServiceAddress = optBadgeServiceAddress <|> badgeServiceAddress cfg,
badgeWebBaseUrl = fromMaybe (badgeWebBaseUrl cfg) optBadgeWebUrl,
badgePublicKeys = if null optBadgeIssuerKeys then badgePublicKeys cfg else M.fromList optBadgeIssuerKeys
}