mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-02 09:02:34 +00:00
store: move badge service app to store service
This commit is contained in:
1 parent
4aaeadd591
commit
a454ba63ea
152 files changed
+464
-462
No files matched your search
@@ -6,11 +6,11 @@ on:
|
||||
- master
|
||||
- stable
|
||||
paths:
|
||||
- "apps/simplex-badge-service/web/**"
|
||||
- "apps/simplex-store-service/web/**"
|
||||
- ".github/workflows/badge-web.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/simplex-badge-service/web/**"
|
||||
- "apps/simplex-store-service/web/**"
|
||||
- ".github/workflows/badge-web.yml"
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/simplex-badge-service/web
|
||||
working-directory: apps/simplex-store-service/web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: apps/simplex-badge-service/web/package-lock.json
|
||||
cache-dependency-path: apps/simplex-store-service/web/package-lock.json
|
||||
|
||||
- run: npm ci
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
- "apps/simplex-chat/**"
|
||||
- "apps/simplex-bot/**"
|
||||
- "apps/simplex-bot-advanced/**"
|
||||
- "apps/simplex-badge-service/**"
|
||||
- "apps/simplex-store-service/**"
|
||||
- "apps/simplex-broadcast-bot/**"
|
||||
- "apps/simplex-directory-service/**"
|
||||
- "tests/**"
|
||||
|
||||
+6
-4
@@ -88,10 +88,12 @@ website/test/stubs-layout-cache/_includes/*.js
|
||||
apps/android/app/release
|
||||
apps/multiplatform/.kotlin/sessions
|
||||
# anywhere in the tree: this file holds a real BTCPay api_key and webhook_secret
|
||||
store_service.ini
|
||||
# a local ini under its name before the store service rename holds the same secrets
|
||||
badge_service.ini
|
||||
|
||||
# Badge service Docker deployment: the Dockerfile, compose, and example config are tracked; the
|
||||
# Store service Docker deployment: the Dockerfile, compose, and example config are tracked; the
|
||||
# live config (a secret) and its data volume are not.
|
||||
scripts/badge-service/badge_service.ini
|
||||
scripts/badge-service/data/
|
||||
scripts/badge-service/web/
|
||||
scripts/store-service/store_service.ini
|
||||
scripts/store-service/data/
|
||||
scripts/store-service/web/
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
module Main where
|
||||
|
||||
import BadgeService.Options (BadgeServiceOpts (..))
|
||||
import BadgeService.Service
|
||||
import StoreService.Options (StoreServiceOpts (..))
|
||||
import StoreService.Service
|
||||
import Control.Logger.Simple (LogConfig (..), LogLevel (..), setLogLevel, withGlobalLogging)
|
||||
import GHC.IO.Encoding (setLocaleEncoding)
|
||||
import Simplex.Chat.Terminal (terminalChatConfig)
|
||||
@@ -17,7 +17,7 @@ main = do
|
||||
mapM_ (`hSetEncoding` utf8) [stdout, stderr]
|
||||
withGlobalLogging LogConfig {lc_file = Nothing, lc_stderr = True} $ do
|
||||
setLogLevel LogWarn
|
||||
opts@BadgeServiceOpts {runCLI} <- welcomeGetOpts
|
||||
opts@StoreServiceOpts {runCLI} <- welcomeGetOpts
|
||||
if runCLI
|
||||
then badgeServiceCLI opts
|
||||
else newServiceState >>= badgeService opts terminalChatConfig
|
||||
then storeServiceCLI opts
|
||||
else newServiceState >>= storeService opts terminalChatConfig
|
||||
@@ -1,8 +1,8 @@
|
||||
# SimpleX badge service
|
||||
# SimpleX store service
|
||||
|
||||
`simplex-badge-service` runs the whole supporter-badge service as one process, two
|
||||
`simplex-store-service` runs the whole store service as one process, two
|
||||
lanes: a SimpleX chat bot that answers service RPC over a double-ratchet contact
|
||||
address, and — only when `--service-config` names a `badge_service.ini` — the badge-codes
|
||||
address, and — only when `--service-config` names a `store_service.ini` — the badge-codes
|
||||
web checkout (BTCPay and Stripe payment, code issuance, the poller). **Without
|
||||
`--service-config` the web listener does not start at all**; the process still runs the
|
||||
chat side and nothing else.
|
||||
@@ -11,7 +11,7 @@ The wire protocol for the RPC side is
|
||||
[`docs/protocol/badges-rpc.md`](../../docs/protocol/badges-rpc.md). The web checkout is
|
||||
specified in
|
||||
[`plans/badges-codes/2026-08-27-badge-codes.md`](../../plans/badges-codes/2026-08-27-badge-codes.md),
|
||||
whose §9 is the `badge_service.ini` reference, with the implementation plan in
|
||||
whose §9 is the `store_service.ini` reference, with the implementation plan in
|
||||
[`plans/badges-codes/2026-08-31-service-btcpay.md`](../../plans/badges-codes/2026-08-31-service-btcpay.md).
|
||||
Earlier plans (`2026-07-30-supporter-badges-v3-ux.md`, `2026-07-31-badges-core-implementation.md`,
|
||||
`2026-08-04-badges-mvp-scope.md`) predate the web checkout and describe the RPC-only
|
||||
@@ -37,13 +37,13 @@ purchase that already exists.
|
||||
Build prerequisites and the general contribution flow are in [`docs/CONTRIBUTING.md`](../../docs/CONTRIBUTING.md).
|
||||
|
||||
```
|
||||
cabal build exe:simplex-badge-service
|
||||
cabal build exe:simplex-store-service
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
simplex-badge-service --help
|
||||
simplex-store-service --help
|
||||
```
|
||||
|
||||
- default (no `--run-cli`): background service mode, no interactive terminal.
|
||||
@@ -62,7 +62,7 @@ The service checks the secret against the configured public key at that index an
|
||||
if they disagree: credentials signed with the wrong key cannot be verified by any client, and the
|
||||
codes redeemed against them would be spent for nothing.
|
||||
|
||||
The key can come from `badge_service.ini` instead:
|
||||
The key can come from `store_service.ini` instead:
|
||||
|
||||
```ini
|
||||
[issuer]
|
||||
@@ -76,40 +76,40 @@ Rotating is a change to both and a restart, once clients trust the new index.
|
||||
|
||||
The command line wins over the file when both `--issuer-key-idx` and `--issuer-secret` are given.
|
||||
Note that a secret passed as a flag is visible to every user on the machine through `ps`, where one
|
||||
in the ini is only as readable as the file: `badge_service.ini` is gitignored and already holds the
|
||||
in the ini is only as readable as the file: `store_service.ini` is gitignored and already holds the
|
||||
provider API keys and webhook secrets.
|
||||
|
||||
Other options:
|
||||
|
||||
- `--service-config INI_FILE`: path to `badge_service.ini`. Omit it to run the chat/RPC side
|
||||
- `--service-config INI_FILE`: path to `store_service.ini`. Omit it to run the chat/RPC side
|
||||
only; the process never starts a web listener without it, and never starts one under
|
||||
`--run-cli`, which parses and validates the whole file (`[listener] static_dir` included) but
|
||||
uses only its `[issuer]` section. An issuer key is still required either way.
|
||||
- `--service-name NAME`: the bot's display name, without `*`s or spaces (default `SimpleX Badges`).
|
||||
- `--client-service`: use the client service certificate.
|
||||
- also accepts the standard SimpleX Chat core options — database path, SMP/XFTP servers,
|
||||
`--socks-proxy`, `--log-level`/`-l`, and the rest — run `simplex-badge-service --help`
|
||||
`--socks-proxy`, `--log-level`/`-l`, and the rest — run `simplex-store-service --help`
|
||||
for the complete list.
|
||||
|
||||
### Running the web checkout
|
||||
|
||||
`badge_service.ini` holds the listener bind address and `static_dir`, an optional
|
||||
`store_service.ini` holds the listener bind address and `static_dir`, an optional
|
||||
`[btcpay]` section (omitting it disables Bitcoin and Monero), an optional `[stripe]`
|
||||
section (omitting it disables card payments), an optional `[group]` section (omitting it
|
||||
turns off the group's commands, though a group created earlier still has its code messages updated)
|
||||
and the poll cadence.
|
||||
`badge_service.ini.example` is the committed template; `badge_service.ini` itself is
|
||||
`store_service.ini.example` is the committed template; `store_service.ini` itself is
|
||||
gitignored, since a real one holds API keys and webhook secrets.
|
||||
|
||||
The full walkthrough is in
|
||||
[`web/README.md`](web/README.md#running-the-real-service-against-this-build). Short version:
|
||||
|
||||
```
|
||||
cd apps/simplex-badge-service/web && npm install && npm run build && cd ../../..
|
||||
cp apps/simplex-badge-service/badge_service.ini.example apps/simplex-badge-service/badge_service.ini
|
||||
cabal run simplex-badge-service -- \
|
||||
cd apps/simplex-store-service/web && npm install && npm run build && cd ../../..
|
||||
cp apps/simplex-store-service/store_service.ini.example apps/simplex-store-service/store_service.ini
|
||||
cabal run simplex-store-service -- \
|
||||
--issuer-key-idx IDX --issuer-secret SECRET \
|
||||
--service-config apps/simplex-badge-service/badge_service.ini
|
||||
--service-config apps/simplex-store-service/store_service.ini
|
||||
```
|
||||
|
||||
`IDX` and `SECRET` must be one of the issuer keys clients already ship, not a fresh
|
||||
@@ -249,7 +249,7 @@ why issuing codes lives in the service rather than in core.
|
||||
|
||||
### From the group
|
||||
|
||||
With `[group]` in `badge_service.ini`, the service manages one group and serves three commands in
|
||||
With `[group]` in `store_service.ini`, the service manages one group and serves three commands in
|
||||
it: `/issue <type> [months <M>] [uses <N>]` and `/bulk <type> [months <M>] count <B>` for moderators
|
||||
and above, `/revoke <code>` for admins and owners. `months` is 1 to 255, `uses` 1 to 1000 and
|
||||
`count` 1 to 100; a value outside these gets the usage reply. A member's role is checked as the
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module BadgeService.Catalog
|
||||
module StoreService.Catalog
|
||||
( OfferInvalid (..),
|
||||
CatalogRefusal (..),
|
||||
offerTotal,
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module BadgeService.Codes
|
||||
module StoreService.Codes
|
||||
( issueOneCode,
|
||||
issueFailedText,
|
||||
revokeBadgeCode,
|
||||
@@ -9,8 +9,8 @@ module BadgeService.Codes
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Store (RevokeResult, insertBadgeCode, revokeCode)
|
||||
import BadgeService.Store.Invoices (truncateToSecond)
|
||||
import StoreService.Store (RevokeResult, insertBadgeCode, revokeCode)
|
||||
import StoreService.Store.Invoices (truncateToSecond)
|
||||
import Data.Int (Int64)
|
||||
import Data.Text (Text)
|
||||
import Data.Time.Clock (getCurrentTime)
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Config
|
||||
module StoreService.Config
|
||||
( ListenerConfig (..),
|
||||
BTCPayConfig (..),
|
||||
StripeConfig (..),
|
||||
@@ -21,7 +21,7 @@ module BadgeService.Config
|
||||
where
|
||||
|
||||
import qualified Control.Exception as E
|
||||
import BadgeService.Log (logWarn)
|
||||
import StoreService.Log (logWarn)
|
||||
import Control.Monad (mfilter)
|
||||
import Data.Attoparsec.Text (Parser, endOfInput, isEndOfLine, parseOnly, satisfy, skipMany, skipSpace, skipWhile)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
+7
-7
@@ -5,7 +5,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
||||
|
||||
module BadgeService.Group
|
||||
module StoreService.Group
|
||||
( ensureManagedGroup,
|
||||
runGroupLane,
|
||||
inertGroupConfig,
|
||||
@@ -24,12 +24,12 @@ module BadgeService.Group
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Codes (issueFailedText, issueOneCode, revokeBadgeCode, singleUse)
|
||||
import BadgeService.Config (GroupConfig (..))
|
||||
import BadgeService.Group.Command (CmdAction (..), GroupCmd (..), groupCmdAction, groupCommands)
|
||||
import BadgeService.Log (logError, logInfo, logWarn)
|
||||
import BadgeService.Store (CodeTracker (..), ManagedGroup (..), RevokeResult (..), clearCodeGroupItems, getCodeTracker, getEditableTrackers, getManagedGroup, insertManagedGroup, markOwnerBootstrapped, setCodeGroupItem)
|
||||
import BadgeService.Store.Invoices (truncateToSecond)
|
||||
import StoreService.Codes (issueFailedText, issueOneCode, revokeBadgeCode, singleUse)
|
||||
import StoreService.Config (GroupConfig (..))
|
||||
import StoreService.Group.Command (CmdAction (..), GroupCmd (..), groupCmdAction, groupCommands)
|
||||
import StoreService.Log (logError, logInfo, logWarn)
|
||||
import StoreService.Store (CodeTracker (..), ManagedGroup (..), RevokeResult (..), clearCodeGroupItems, getCodeTracker, getEditableTrackers, getManagedGroup, insertManagedGroup, markOwnerBootstrapped, setCodeGroupItem)
|
||||
import StoreService.Store.Invoices (truncateToSecond)
|
||||
import Control.Concurrent.STM (TQueue, atomically, flushTQueue, readTQueue, readTVarIO, writeTQueue)
|
||||
import Control.Monad (forM_, forever, mfilter, replicateM, unless, void)
|
||||
import Control.Monad.Except (runExceptT)
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module BadgeService.Group.Command
|
||||
module StoreService.Group.Command
|
||||
( GroupCmd (..),
|
||||
CmdAction (..),
|
||||
groupCmdAction,
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
-- | The SMP agent logs unconditionally at info on the global logger, so the service keeps its own info lines on this separate channel when that logger is raised to Warn.
|
||||
module BadgeService.Log
|
||||
module StoreService.Log
|
||||
( logInfo,
|
||||
logWarn,
|
||||
logError,
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Names.Chain
|
||||
module StoreService.Names.Chain
|
||||
( ChainClient (..),
|
||||
ChainError (..),
|
||||
TxHash (..),
|
||||
@@ -11,7 +11,7 @@ module BadgeService.Names.Chain
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Names.Registration (RegisterIntent, commitCalldata, registerWithCreditCalldata)
|
||||
import StoreService.Names.Registration (RegisterIntent, commitCalldata, registerWithCreditCalldata)
|
||||
import Control.Concurrent.STM (TVar)
|
||||
import Control.Exception (try)
|
||||
import Control.Monad.Except (ExceptT, liftEither, runExceptT, throwError)
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module BadgeService.Names.Registration
|
||||
module StoreService.Names.Registration
|
||||
( Registration (..),
|
||||
RegisterIntent (..),
|
||||
sponsorDomain,
|
||||
+18
-18
@@ -3,15 +3,15 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module BadgeService.Options
|
||||
( BadgeServiceOpts (..),
|
||||
getBadgeServiceOpts,
|
||||
badgeServiceOpts,
|
||||
module StoreService.Options
|
||||
( StoreServiceOpts (..),
|
||||
getStoreServiceOpts,
|
||||
storeServiceOpts,
|
||||
mkChatOpts,
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Config (BadgeIssuerKey (..))
|
||||
import StoreService.Config (BadgeIssuerKey (..))
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.Text as T
|
||||
import Options.Applicative
|
||||
@@ -19,7 +19,7 @@ import Simplex.Chat.Controller (updateStr, versionNumber, versionString)
|
||||
import Simplex.Chat.Options (ChatCmdLog (..), ChatOpts (..), CoreChatOpts, CreateBotOpts (..), coreChatOptsP)
|
||||
import Simplex.Messaging.Encoding.String (strDecode)
|
||||
|
||||
data BadgeServiceOpts = BadgeServiceOpts
|
||||
data StoreServiceOpts = StoreServiceOpts
|
||||
{ coreOptions :: CoreChatOpts,
|
||||
serviceName :: T.Text,
|
||||
clientService :: Bool,
|
||||
@@ -30,14 +30,14 @@ data BadgeServiceOpts = BadgeServiceOpts
|
||||
testing :: Bool
|
||||
}
|
||||
|
||||
badgeServiceOpts :: FilePath -> FilePath -> Parser BadgeServiceOpts
|
||||
badgeServiceOpts appDir defaultDbName = do
|
||||
storeServiceOpts :: FilePath -> FilePath -> Parser StoreServiceOpts
|
||||
storeServiceOpts appDir defaultDbName = do
|
||||
coreOptions <- coreChatOptsP appDir defaultDbName
|
||||
serviceName <-
|
||||
strOption
|
||||
( long "service-name"
|
||||
<> metavar "SERVICE_NAME"
|
||||
<> help "The display name of the badge service bot, without *'s and spaces (SimpleX Badges)"
|
||||
<> help "The display name of the store service bot, without *'s and spaces (SimpleX Badges)"
|
||||
<> value "SimpleX Badges"
|
||||
)
|
||||
clientService <-
|
||||
@@ -53,14 +53,14 @@ badgeServiceOpts appDir defaultDbName = do
|
||||
runCLI <-
|
||||
switch
|
||||
( long "run-cli"
|
||||
<> help "Run badge service as CLI"
|
||||
<> help "Run store service as CLI"
|
||||
)
|
||||
serviceConfigFile <-
|
||||
optional
|
||||
( strOption
|
||||
( long "service-config"
|
||||
<> metavar "INI_FILE"
|
||||
<> help "Path to badge_service.ini: parsed in full in every mode, but --run-cli starts no web listener"
|
||||
<> help "Path to store_service.ini: parsed in full in every mode, but --run-cli starts no web listener"
|
||||
)
|
||||
)
|
||||
issuerKeyIdx <-
|
||||
@@ -80,7 +80,7 @@ badgeServiceOpts appDir defaultDbName = do
|
||||
<> help "Issuer secret from `simplex-chat badge keygen` (base64url)"
|
||||
)
|
||||
pure
|
||||
BadgeServiceOpts
|
||||
StoreServiceOpts
|
||||
{ coreOptions,
|
||||
serviceName = T.pack serviceName,
|
||||
clientService,
|
||||
@@ -94,19 +94,19 @@ badgeServiceOpts appDir defaultDbName = do
|
||||
testing = False
|
||||
}
|
||||
|
||||
getBadgeServiceOpts :: FilePath -> FilePath -> IO BadgeServiceOpts
|
||||
getBadgeServiceOpts appDir defaultDbName =
|
||||
getStoreServiceOpts :: FilePath -> FilePath -> IO StoreServiceOpts
|
||||
getStoreServiceOpts appDir defaultDbName =
|
||||
execParser $
|
||||
info
|
||||
(helper <*> versionOption <*> badgeServiceOpts appDir defaultDbName)
|
||||
(header versionStr <> fullDesc <> progDesc "Start SimpleX Badge Service with DB_FILE options")
|
||||
(helper <*> versionOption <*> storeServiceOpts appDir defaultDbName)
|
||||
(header versionStr <> fullDesc <> progDesc "Start SimpleX Store Service with DB_FILE options")
|
||||
where
|
||||
versionStr = versionString versionNumber
|
||||
versionOption = infoOption versionAndUpdate (long "version" <> short 'v' <> help "Show version")
|
||||
versionAndUpdate = versionStr <> "\n" <> updateStr
|
||||
|
||||
mkChatOpts :: BadgeServiceOpts -> ChatOpts
|
||||
mkChatOpts BadgeServiceOpts {coreOptions, serviceName, clientService} =
|
||||
mkChatOpts :: StoreServiceOpts -> ChatOpts
|
||||
mkChatOpts StoreServiceOpts {coreOptions, serviceName, clientService} =
|
||||
ChatOpts
|
||||
{ coreOptions,
|
||||
chatCmd = "",
|
||||
+4
-4
@@ -2,11 +2,11 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module BadgeService.Orders (settleOrder, decide, codeLifetime) where
|
||||
module StoreService.Orders (settleOrder, decide, codeLifetime) where
|
||||
|
||||
import BadgeService.Providers (Funded (..), PaymentSignal (..), Received (..))
|
||||
import BadgeService.Store.Invoices (InvoicePayment (..), InvoiceRow (..), markCodePaid, paymentStatusText, settlementCodeHash, settlementInvoice, truncateToSecond, updateInvoiceStatus, upsertPayment)
|
||||
import BadgeService.Waiters (Waiters, publish, publishPayment)
|
||||
import StoreService.Providers (Funded (..), PaymentSignal (..), Received (..))
|
||||
import StoreService.Store.Invoices (InvoicePayment (..), InvoiceRow (..), markCodePaid, paymentStatusText, settlementCodeHash, settlementInvoice, truncateToSecond, updateInvoiceStatus, upsertPayment)
|
||||
import StoreService.Waiters (Waiters, publish, publishPayment)
|
||||
import Control.Concurrent.STM (atomically)
|
||||
import Control.Monad (when)
|
||||
import Data.ByteString (ByteString)
|
||||
+7
-7
@@ -4,7 +4,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Poller
|
||||
module StoreService.Poller
|
||||
( PollerEnv (..),
|
||||
ReadHints,
|
||||
newReadHints,
|
||||
@@ -27,14 +27,14 @@ module BadgeService.Poller
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Config (PollConfig (..))
|
||||
import BadgeService.Orders (decide, settleOrder)
|
||||
import BadgeService.Providers (ListPass (..), PaymentSignal (..), Provider (..), ProviderError (..), Received (..), expiresItself, settleWindow)
|
||||
import BadgeService.Store.Invoices (InvoiceRow (..), OverdueInvoice (..), expireOverdue, getInvoiceByProviderRef, overdueInvoices, providerText, unpaidRefs)
|
||||
import BadgeService.Waiters (Waiters, publish, waitingCount, waitingCountSTM)
|
||||
import StoreService.Config (PollConfig (..))
|
||||
import StoreService.Orders (decide, settleOrder)
|
||||
import StoreService.Providers (ListPass (..), PaymentSignal (..), Provider (..), ProviderError (..), Received (..), expiresItself, settleWindow)
|
||||
import StoreService.Store.Invoices (InvoiceRow (..), OverdueInvoice (..), expireOverdue, getInvoiceByProviderRef, overdueInvoices, providerText, unpaidRefs)
|
||||
import StoreService.Waiters (Waiters, publish, waitingCount, waitingCountSTM)
|
||||
import Control.Concurrent.STM
|
||||
import Control.Exception (SomeAsyncException, SomeException, fromException, throwIO, try)
|
||||
import BadgeService.Log (logError, logInfo, logWarn)
|
||||
import StoreService.Log (logError, logInfo, logWarn)
|
||||
import Control.Monad (filterM, forever, unless, void, when)
|
||||
import Data.List (find, partition, sortOn)
|
||||
import Data.Map.Strict (Map)
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
|
||||
module BadgeService.Providers
|
||||
module StoreService.Providers
|
||||
( ProviderError (..),
|
||||
WebhookError (..),
|
||||
Received (..),
|
||||
+4
-4
@@ -3,7 +3,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Providers.BTCPay
|
||||
module StoreService.Providers.BTCPay
|
||||
( btcpayProvider,
|
||||
btcMethodId,
|
||||
xmrMethodId,
|
||||
@@ -16,8 +16,8 @@ module BadgeService.Providers.BTCPay
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Config (BTCPayConfig (..), speedPolicyName)
|
||||
import BadgeService.Providers
|
||||
import StoreService.Config (BTCPayConfig (..), speedPolicyName)
|
||||
import StoreService.Providers
|
||||
( ListPass (..),
|
||||
OrderDraft (..),
|
||||
PaymentSignal (..),
|
||||
@@ -30,7 +30,7 @@ import BadgeService.Providers
|
||||
WebhookError (..),
|
||||
)
|
||||
import Control.Exception (try)
|
||||
import BadgeService.Log (logError, logInfo, logWarn)
|
||||
import StoreService.Log (logError, logInfo, logWarn)
|
||||
import Control.Monad (unless)
|
||||
import Crypto.Hash (Digest, SHA256)
|
||||
import Crypto.MAC.HMAC (HMAC, hmac, hmacGetDigest)
|
||||
+3
-3
@@ -3,15 +3,15 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Providers.Stripe
|
||||
module StoreService.Providers.Stripe
|
||||
( stripeProvider,
|
||||
signalOf,
|
||||
IntentRead (..),
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Config (StripeConfig (..))
|
||||
import BadgeService.Providers
|
||||
import StoreService.Config (StripeConfig (..))
|
||||
import StoreService.Providers
|
||||
( ListPass (..),
|
||||
OrderDraft (..),
|
||||
PaymentSignal (..),
|
||||
+43
-43
@@ -4,37 +4,37 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
|
||||
module BadgeService.Service
|
||||
module StoreService.Service
|
||||
( ServiceState (..),
|
||||
newServiceState,
|
||||
welcomeGetOpts,
|
||||
checkIssuerKey,
|
||||
badgeService,
|
||||
badgeServiceCLI,
|
||||
badgeServiceResponse,
|
||||
storeService,
|
||||
storeServiceCLI,
|
||||
storeServiceResponse,
|
||||
badgeErrorRetryAfter,
|
||||
IssueCodeOpts (..),
|
||||
issueBadgeCode,
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Catalog (defaultCatalog)
|
||||
import BadgeService.Codes (issueFailedText, issueOneCode, singleUse)
|
||||
import BadgeService.Config (BadgeIssuerKey (..), ServiceConfig (..), readServiceConfig)
|
||||
import BadgeService.Group (GroupEvent, ensureManagedGroup, groupEvent, hasTracker, refreshTracker, revokeWithTracker, runGroupLane)
|
||||
import BadgeService.Group.Command (badgeTypeP, codeP, maxMonths, textTokenP)
|
||||
import BadgeService.Options
|
||||
import BadgeService.Poller (newPollerEnv, newReadHints, runPoller)
|
||||
import BadgeService.Providers.BTCPay (btcpayProvider)
|
||||
import BadgeService.Providers.Stripe (stripeProvider)
|
||||
import BadgeService.Store
|
||||
import BadgeService.Store.Invoices (seedCatalog, truncateToSecond)
|
||||
import BadgeService.Store.Migrate (runBadgeServiceMigrations)
|
||||
import BadgeService.Waiters (Waiters, newWaiters)
|
||||
import BadgeService.Web.Server (exportWebapp, newWebEnv, runWebListener)
|
||||
import StoreService.Catalog (defaultCatalog)
|
||||
import StoreService.Codes (issueFailedText, issueOneCode, singleUse)
|
||||
import StoreService.Config (BadgeIssuerKey (..), ServiceConfig (..), readServiceConfig)
|
||||
import StoreService.Group (GroupEvent, ensureManagedGroup, groupEvent, hasTracker, refreshTracker, revokeWithTracker, runGroupLane)
|
||||
import StoreService.Group.Command (badgeTypeP, codeP, maxMonths, textTokenP)
|
||||
import StoreService.Options
|
||||
import StoreService.Poller (newPollerEnv, newReadHints, runPoller)
|
||||
import StoreService.Providers.BTCPay (btcpayProvider)
|
||||
import StoreService.Providers.Stripe (stripeProvider)
|
||||
import StoreService.Store
|
||||
import StoreService.Store.Invoices (seedCatalog, truncateToSecond)
|
||||
import StoreService.Store.Migrate (runStoreServiceMigrations)
|
||||
import StoreService.Waiters (Waiters, newWaiters)
|
||||
import StoreService.Web.Server (exportWebapp, newWebEnv, runWebListener)
|
||||
import Control.Applicative (optional)
|
||||
import Control.Concurrent.STM
|
||||
import BadgeService.Log (logError, logInfo)
|
||||
import StoreService.Log (logError, logInfo)
|
||||
import Control.Monad
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import qualified Data.Aeson as J
|
||||
@@ -83,22 +83,22 @@ newServiceState = do
|
||||
groupEventQ <- newTQueueIO
|
||||
pure ServiceState {serviceCC, serviceRequestQ, groupEventQ}
|
||||
|
||||
welcomeGetOpts :: IO BadgeServiceOpts
|
||||
welcomeGetOpts :: IO StoreServiceOpts
|
||||
welcomeGetOpts = do
|
||||
appDir <- getAppUserDataDirectory "simplex"
|
||||
opts@BadgeServiceOpts {coreOptions, testing, serviceName} <- getBadgeServiceOpts appDir "simplex_badge_service"
|
||||
opts@StoreServiceOpts {coreOptions, testing, serviceName} <- getStoreServiceOpts appDir "simplex_badge_service"
|
||||
unless testing $ do
|
||||
putStrLn $ "SimpleX Badge Service v" ++ versionNumber
|
||||
putStrLn $ "SimpleX Store Service v" ++ versionNumber
|
||||
printDbOpts coreOptions
|
||||
putStrLn $ "Service name: " ++ T.unpack serviceName
|
||||
pure opts
|
||||
|
||||
checkIssuerKey :: BadgeServiceOpts -> Maybe ServiceConfig -> ChatConfig -> IO (Either String BadgeIssuerKey)
|
||||
checkIssuerKey BadgeServiceOpts {issuerKey} serviceCfg cfg = case issuerKey of
|
||||
checkIssuerKey :: StoreServiceOpts -> Maybe ServiceConfig -> ChatConfig -> IO (Either String BadgeIssuerKey)
|
||||
checkIssuerKey StoreServiceOpts {issuerKey} serviceCfg cfg = case issuerKey of
|
||||
Left e -> pure (Left e)
|
||||
Right (Just k) -> checkOne cfg k
|
||||
Right Nothing -> case serviceCfg >>= issuer of
|
||||
Nothing -> pure $ Left "an issuer key is required - pass --issuer-key-idx and --issuer-secret, or add an [issuer] section to badge_service.ini (see `simplex-chat badge keygen`)"
|
||||
Nothing -> pure $ Left "an issuer key is required - pass --issuer-key-idx and --issuer-secret, or add an [issuer] section to store_service.ini (see `simplex-chat badge keygen`)"
|
||||
Just k -> checkOne cfg k
|
||||
|
||||
checkOne :: ChatConfig -> BadgeIssuerKey -> IO (Either String BadgeIssuerKey)
|
||||
@@ -110,7 +110,7 @@ checkOne ChatConfig {badgePublicKeys} k@BadgeIssuerKey {keyIdx, secretKey} =
|
||||
Just _ -> Left $ "issuer secret does not match the configured key at index " <> show keyIdx <> ", its public key is " <> T.unpack (safeDecodeUtf8 $ strEncode pk)
|
||||
Nothing -> Left $ "no configured badge key at index " <> show keyIdx <> ", clients could not verify what this service signs"
|
||||
|
||||
requireIssuerKey :: BadgeServiceOpts -> Maybe ServiceConfig -> ChatConfig -> IO BadgeIssuerKey
|
||||
requireIssuerKey :: StoreServiceOpts -> Maybe ServiceConfig -> ChatConfig -> IO BadgeIssuerKey
|
||||
requireIssuerKey opts serviceCfg cfg =
|
||||
checkIssuerKey opts serviceCfg cfg >>= either (\e -> putStrLn ("Error: " <> e) >> exitFailure) pure
|
||||
|
||||
@@ -120,8 +120,8 @@ readConfigOrExit path =
|
||||
Left e -> putStrLn (path <> ": " <> e) >> exitFailure
|
||||
Right sc -> pure sc
|
||||
|
||||
badgeService :: BadgeServiceOpts -> ChatConfig -> ServiceState -> IO ()
|
||||
badgeService opts@BadgeServiceOpts {serviceConfigFile} cfg env = do
|
||||
storeService :: StoreServiceOpts -> ChatConfig -> ServiceState -> IO ()
|
||||
storeService opts@StoreServiceOpts {serviceConfigFile} cfg env = do
|
||||
serviceCfg <- traverse readConfigOrExit serviceConfigFile
|
||||
key <- requireIssuerKey opts serviceCfg cfg
|
||||
waiters <- newWaiters
|
||||
@@ -171,8 +171,8 @@ seedServiceCatalog st = do
|
||||
"badge catalog: " <> tshow (length prices) <> " prices and " <> tshow (length offers) <> " offers compiled in, "
|
||||
<> tshow seededPrices <> " prices and " <> tshow seededOffers <> " offers inserted"
|
||||
|
||||
badgeServiceCLI :: BadgeServiceOpts -> IO ()
|
||||
badgeServiceCLI opts@BadgeServiceOpts {serviceConfigFile} = do
|
||||
storeServiceCLI :: StoreServiceOpts -> IO ()
|
||||
storeServiceCLI opts@StoreServiceOpts {serviceConfigFile} = do
|
||||
serviceCfg <- traverse readConfigOrExit serviceConfigFile
|
||||
key <- requireIssuerKey opts serviceCfg terminalChatConfig
|
||||
env <- newServiceState
|
||||
@@ -248,12 +248,12 @@ processQueuedRequests key trackerQ_ env = do
|
||||
(u, reqId, sigKey, reqData) <- atomically $ readTQueue $ serviceRequestQ env
|
||||
handleServiceRequest key cc trackerQ_ u reqId sigKey reqData
|
||||
|
||||
badgePreStartHook :: BadgeServiceOpts -> ChatController -> IO ()
|
||||
badgePreStartHook :: StoreServiceOpts -> ChatController -> IO ()
|
||||
badgePreStartHook opts ChatController {config, chatStore} =
|
||||
runBadgeServiceMigrations opts config chatStore
|
||||
runStoreServiceMigrations opts config chatStore
|
||||
|
||||
badgePostStartHook :: BadgeServiceOpts -> ServiceState -> ChatController -> IO ()
|
||||
badgePostStartHook BadgeServiceOpts {noAddress, testing} env cc = do
|
||||
badgePostStartHook :: StoreServiceOpts -> ServiceState -> ChatController -> IO ()
|
||||
badgePostStartHook StoreServiceOpts {noAddress, testing} env cc = do
|
||||
-- Core starts this False and gates service request delivery on it, so the hook must set it.
|
||||
atomically $ writeTVar (processServiceRequests cc) True
|
||||
readTVarIO (currentUser cc) >>= \case
|
||||
@@ -266,11 +266,11 @@ badgePostStartHook BadgeServiceOpts {noAddress, testing} env cc = do
|
||||
handleServiceRequest :: BadgeIssuerKey -> ChatController -> Maybe (TQueue GroupEvent) -> User -> AgentInvId -> Maybe C.PublicKeyEd25519 -> J.Object -> IO ()
|
||||
handleServiceRequest key cc trackerQ_ User {userId} reqId sigKey reqData = do
|
||||
let reqIdT = safeDecodeUtf8 (strEncode reqId)
|
||||
logInfo $ "badge service request " <> reqIdT
|
||||
resp <- badgeServiceResponse key cc trackerQ_ sigKey reqData
|
||||
logInfo $ "store service request " <> reqIdT
|
||||
resp <- storeServiceResponse key cc trackerQ_ sigKey reqData
|
||||
sendChatCmd cc (APISendServiceResponse userId reqId (responseObject resp)) >>= \case
|
||||
Right _ -> pure ()
|
||||
Left e -> logError $ "badge service response failed for " <> reqIdT <> ": " <> tshow e
|
||||
Left e -> logError $ "store service response failed for " <> reqIdT <> ": " <> tshow e
|
||||
|
||||
responseObject :: StoreServiceResponse -> J.Object
|
||||
responseObject r = case J.toJSON r of
|
||||
@@ -289,8 +289,8 @@ badgeErrorRetryAfter = \case
|
||||
|
||||
|
||||
-- | The agent verified the signature, so sigKey is a key the sender holds; a differing purchaseKey would let a client claim a purchase it cannot sign for.
|
||||
badgeServiceResponse :: BadgeIssuerKey -> ChatController -> Maybe (TQueue GroupEvent) -> Maybe C.PublicKeyEd25519 -> J.Object -> IO StoreServiceResponse
|
||||
badgeServiceResponse key cc trackerQ_ sigKey reqData = case J.fromJSON (J.Object reqData) of
|
||||
storeServiceResponse :: BadgeIssuerKey -> ChatController -> Maybe (TQueue GroupEvent) -> Maybe C.PublicKeyEd25519 -> J.Object -> IO StoreServiceResponse
|
||||
storeServiceResponse key cc trackerQ_ sigKey reqData = case J.fromJSON (J.Object reqData) of
|
||||
J.Error _ -> pure $ errorResponse STEBadRequest
|
||||
J.Success StoreServiceRequest {version, purchaseKey, request}
|
||||
| not (version `isCompatible` supportedStoreServiceVRange) -> pure $ errorResponse STEUnsupportedVersion
|
||||
@@ -348,7 +348,7 @@ redeemCode key cc trackerQ_ purchaseKey masterKey codeText = case parseBadgeCode
|
||||
case issueEntry now issueUuid granted of
|
||||
Nothing -> pure $ errorResponse STEInternal
|
||||
Just issued -> credentialForEntry key masterKey issued >>= \case
|
||||
Left e -> logError ("badge service signing failed: " <> T.pack e) $> errorResponse STEInternal
|
||||
Left e -> logError ("store service signing failed: " <> T.pack e) $> errorResponse STEInternal
|
||||
Right signed -> do
|
||||
-- If the code was revoked or used up while signing, the claim fails. Read the code again to tell the client why.
|
||||
r <- withDB "writeCodeRedemption" cc $ \db ->
|
||||
@@ -356,7 +356,7 @@ redeemCode key cc trackerQ_ purchaseKey masterKey codeText = case parseBadgeCode
|
||||
Nothing ->
|
||||
readCode now code db >>= \case
|
||||
Left resp -> pure (resp, False)
|
||||
Right _ -> logError "badge service: redeeming a code failed, but the code has uses left and is not revoked" $> (errorResponse STEInternal, False)
|
||||
Right _ -> logError "store service: redeeming a code failed, but the code has uses left and is not revoked" $> (errorResponse STEInternal, False)
|
||||
Just purchaseId -> liftIO $ do
|
||||
appendLedgerPlan db purchaseId [granted] $ Just $ issuanceAfter granted signed
|
||||
entries_ <- getLedgerEntries db purchaseId 0
|
||||
@@ -377,7 +377,7 @@ redeemCode key cc trackerQ_ purchaseKey masterKey codeText = case parseBadgeCode
|
||||
<$> getLedgerEntries db badgePurchaseId 0
|
||||
-- code_used would make the client drop its keys, so the holder could never get the badge back.
|
||||
KeyRedeemedUnreadable ->
|
||||
logError "badge service: a redeemed code's credential is missing or unreadable" $> Left (errorResponse STEInternal)
|
||||
logError "store service: a redeemed code's credential is missing or unreadable" $> Left (errorResponse STEInternal)
|
||||
KeyUnredeemed
|
||||
-- Revoked is checked first, so it answers as if the code never existed.
|
||||
| Just _ <- revokedAt -> pure $ Left $ errorResponse STECodeInvalid
|
||||
@@ -406,7 +406,7 @@ issueBadgeCmd key cc purchaseKey BadgeBalance {lastEntry} = do
|
||||
Nothing -> writeIssued badgePurchaseId tip (maybeToList lapsed) now Nothing
|
||||
Just e ->
|
||||
credentialForEntry key masterKey e >>= \case
|
||||
Left err -> logError ("badge service signing failed: " <> T.pack err) $> errorResponse STEInternal
|
||||
Left err -> logError ("store service signing failed: " <> T.pack err) $> errorResponse STEInternal
|
||||
Right signed ->
|
||||
writeIssued badgePurchaseId tip (maybeToList lapsed) now $ Just $ issuanceAfter current signed
|
||||
where
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Store
|
||||
module StoreService.Store
|
||||
( IssuedCode (..),
|
||||
KeyRedemption (..),
|
||||
KeyPurchase (..),
|
||||
@@ -36,7 +36,7 @@ module BadgeService.Store
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Store.Invoices (executeChanging)
|
||||
import StoreService.Store.Invoices (executeChanging)
|
||||
import qualified Data.Aeson as J
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
|
||||
module BadgeService.Store.Invoices
|
||||
module StoreService.Store.Invoices
|
||||
( InvoiceRow (..),
|
||||
InvoicePayment (..),
|
||||
paymentHolds,
|
||||
@@ -63,12 +63,12 @@ import Simplex.Messaging.Encoding.String (textEncode)
|
||||
import Simplex.Messaging.Util (safeDecodeUtf8, tshow)
|
||||
|
||||
#if defined(dbPostgres)
|
||||
import BadgeService.Store.Postgres.Migrations (servicePrefix, withPrefix)
|
||||
import StoreService.Store.Postgres.Migrations (servicePrefix, withPrefix)
|
||||
import Database.PostgreSQL.Simple (Only (..), Query, ToRow, (:.) (..))
|
||||
import qualified Database.PostgreSQL.Simple as PSQL
|
||||
import Database.PostgreSQL.Simple.Errors (ConstraintViolation (..), constraintViolation)
|
||||
#else
|
||||
import BadgeService.Store.SQLite.Migrations (servicePrefix, withPrefix)
|
||||
import StoreService.Store.SQLite.Migrations (servicePrefix, withPrefix)
|
||||
import Database.SQLite.Simple (Only (..), Query, ToRow, (:.) (..))
|
||||
import qualified Database.SQLite.Simple as SQL
|
||||
#endif
|
||||
+10
-10
@@ -3,12 +3,12 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module BadgeService.Store.Migrate
|
||||
( runBadgeServiceMigrations,
|
||||
module StoreService.Store.Migrate
|
||||
( runStoreServiceMigrations,
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Options
|
||||
import StoreService.Options
|
||||
import Simplex.Chat.Controller (ChatConfig (..))
|
||||
import Simplex.Chat.Options (CoreChatOpts (..))
|
||||
import Simplex.Chat.Options.DB
|
||||
@@ -18,22 +18,22 @@ import Simplex.Messaging.Agent.Store.Shared (MigrationConfig (..), MigrationConf
|
||||
import System.Exit (exitFailure)
|
||||
|
||||
#if defined(dbPostgres)
|
||||
import BadgeService.Store.Postgres.Migrations
|
||||
import StoreService.Store.Postgres.Migrations
|
||||
#else
|
||||
import BadgeService.Store.SQLite.Migrations
|
||||
import StoreService.Store.SQLite.Migrations
|
||||
#endif
|
||||
|
||||
runBadgeServiceMigrations :: BadgeServiceOpts -> ChatConfig -> DBStore -> IO ()
|
||||
runBadgeServiceMigrations opts ChatConfig {confirmMigrations} chatStore =
|
||||
runStoreServiceMigrations :: StoreServiceOpts -> ChatConfig -> DBStore -> IO ()
|
||||
runStoreServiceMigrations opts ChatConfig {confirmMigrations} chatStore =
|
||||
migrateDBSchema
|
||||
chatStore
|
||||
(toDBOpts dbOptions chatSuffix False [])
|
||||
(Just "sx_badge_service_migrations")
|
||||
badgeServiceSchemaMigrations
|
||||
storeServiceSchemaMigrations
|
||||
MigrationConfig {confirm, backupPath = Nothing}
|
||||
>>= either (exit . ("badge service migrations " <>) . show) pure
|
||||
>>= either (exit . ("store service migrations " <>) . show) pure
|
||||
where
|
||||
BadgeServiceOpts {coreOptions = CoreChatOpts {dbOptions, yesToUpMigrations}} = opts
|
||||
StoreServiceOpts {coreOptions = CoreChatOpts {dbOptions, yesToUpMigrations}} = opts
|
||||
confirm = if confirmMigrations == MCConsole && yesToUpMigrations then MCYesUp else confirmMigrations
|
||||
|
||||
exit :: String -> IO a
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module BadgeService.Store.Postgres.Migrations (badgeServiceSchemaMigrations, servicePrefix, withPrefix) where
|
||||
module StoreService.Store.Postgres.Migrations (storeServiceSchemaMigrations, servicePrefix, withPrefix) where
|
||||
|
||||
import Data.List (sortOn)
|
||||
import Data.Text (Text)
|
||||
@@ -10,8 +10,8 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges (badgeSchema
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
badgeServiceSchemaMigrations :: [Migration]
|
||||
badgeServiceSchemaMigrations = sortOn name $ map migration schemaMigrations
|
||||
storeServiceSchemaMigrations :: [Migration]
|
||||
storeServiceSchemaMigrations = sortOn name $ map migration schemaMigrations
|
||||
where
|
||||
migration (name, up, down) = Migration {name, up, down}
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module BadgeService.Store.SQLite.Migrations (badgeServiceSchemaMigrations, servicePrefix, withPrefix) where
|
||||
module StoreService.Store.SQLite.Migrations (storeServiceSchemaMigrations, servicePrefix, withPrefix) where
|
||||
|
||||
import Data.List (sortOn)
|
||||
import Data.Text (Text)
|
||||
@@ -11,8 +11,8 @@ import Database.SQLite.Simple.QQ (sql)
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
badgeServiceSchemaMigrations :: [Migration]
|
||||
badgeServiceSchemaMigrations = sortOn name $ map migration schemaMigrations
|
||||
storeServiceSchemaMigrations :: [Migration]
|
||||
storeServiceSchemaMigrations = sortOn name $ map migration schemaMigrations
|
||||
where
|
||||
migration (name, up, down) = Migration {name, up = fromQuery up, down = fromQuery <$> down}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
|
||||
module BadgeService.Waiters (Waiters, Seen, newWaiters, publish, publishPayment, awaitStatus, waitingCount, waitingCountSTM) where
|
||||
module StoreService.Waiters (Waiters, Seen, newWaiters, publish, publishPayment, awaitStatus, waitingCount, waitingCountSTM) where
|
||||
|
||||
import Control.Concurrent.STM
|
||||
import Control.Exception (bracket)
|
||||
+10
-10
@@ -3,7 +3,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
module BadgeService.Web.Server
|
||||
module StoreService.Web.Server
|
||||
( WebEnv (..),
|
||||
Limit (..),
|
||||
newWebEnv,
|
||||
@@ -19,15 +19,15 @@ module BadgeService.Web.Server
|
||||
)
|
||||
where
|
||||
|
||||
import BadgeService.Catalog (PricedOffer (..), priceOffer)
|
||||
import BadgeService.Config (BTCPayConfig (..), ListenerConfig (..), ServiceConfig (..), SpeedPolicy (..), StripeConfig (..), defaultExpiryMinutes, defaultSessionMinutes)
|
||||
import BadgeService.Poller (ReadHints, queueReadHint)
|
||||
import BadgeService.Providers (OrderDraft (..), Provider (..), ProviderError (..), ProviderInvoice (..), WebhookError (..))
|
||||
import BadgeService.Store.Invoices (CreateError (..), InvoicePayment (..), InvoiceRow (..), NewInvoice (..), cancelOpenInvoice, codeHashExists, createInvoiceRows, cryptoCurrencyText, getInvoice, getInvoiceByProviderRef, invoiceStatusText, newInvoiceId, paymentHolds, readCatalogRows, textToInvoiceStatus, truncateToSecond)
|
||||
import BadgeService.Waiters (Seen, Waiters, awaitStatus, publish)
|
||||
import StoreService.Catalog (PricedOffer (..), priceOffer)
|
||||
import StoreService.Config (BTCPayConfig (..), ListenerConfig (..), ServiceConfig (..), SpeedPolicy (..), StripeConfig (..), defaultExpiryMinutes, defaultSessionMinutes)
|
||||
import StoreService.Poller (ReadHints, queueReadHint)
|
||||
import StoreService.Providers (OrderDraft (..), Provider (..), ProviderError (..), ProviderInvoice (..), WebhookError (..))
|
||||
import StoreService.Store.Invoices (CreateError (..), InvoicePayment (..), InvoiceRow (..), NewInvoice (..), cancelOpenInvoice, codeHashExists, createInvoiceRows, cryptoCurrencyText, getInvoice, getInvoiceByProviderRef, invoiceStatusText, newInvoiceId, paymentHolds, readCatalogRows, textToInvoiceStatus, truncateToSecond)
|
||||
import StoreService.Waiters (Seen, Waiters, awaitStatus, publish)
|
||||
import Control.Concurrent.STM
|
||||
import qualified Control.Exception as E
|
||||
import BadgeService.Log (logError, logInfo, logWarn)
|
||||
import StoreService.Log (logError, logInfo, logWarn)
|
||||
import Control.Monad (forM_, when)
|
||||
import Data.Aeson ((.=))
|
||||
import qualified Data.Aeson as J
|
||||
@@ -336,7 +336,7 @@ exportWebapp lc@ListenerConfig {lStaticDir, lWebappExportDir} stripeCfg =
|
||||
emptyDir out
|
||||
copyTree lStaticDir out
|
||||
prepareShell lc stripeCfg >>= mapM_ (LB.writeFile (out </> "index.html"))
|
||||
logInfo ("badge service: exported the webapp to " <> T.pack out)
|
||||
logInfo ("store service: exported the webapp to " <> T.pack out)
|
||||
|
||||
-- | Contents only: the directory itself may be a bind or volume mount point that cannot be removed.
|
||||
emptyDir :: FilePath -> IO ()
|
||||
@@ -735,7 +735,7 @@ runWebListener env = do
|
||||
warnIfHeaderTrustIsExposed :: ListenerConfig -> IO ()
|
||||
warnIfHeaderTrustIsExposed ListenerConfig {lHost, lTrustForwardedFor} =
|
||||
when (lTrustForwardedFor && not (isLoopbackHost lHost)) $
|
||||
logWarn ("badge service: trust_forwarded_for is on and the listener binds " <> lHost <> "; every caller can then choose its own rate limit bucket unless a proxy sets the header")
|
||||
logWarn ("store service: trust_forwarded_for is on and the listener binds " <> lHost <> "; every caller can then choose its own rate limit bucket unless a proxy sets the header")
|
||||
|
||||
isLoopbackHost :: Text -> Bool
|
||||
isLoopbackHost h = h `elem` ["127.0.0.1", "::1", "localhost"]
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
; the long poll's `wait`, `seenPaid` and `seenFull` all travel in it
|
||||
host = 127.0.0.1
|
||||
port = 8080
|
||||
; required: the directory `npm run build` writes in apps/simplex-badge-service/web
|
||||
static_dir = ./apps/simplex-badge-service/web/dist
|
||||
; required: the directory `npm run build` writes in apps/simplex-store-service/web
|
||||
static_dir = ./apps/simplex-store-service/web/dist
|
||||
; serve the webapp over HTTP. On (default) is the all-in-one deployment. Off is a split deployment
|
||||
; where a reverse proxy serves the exported folder and this service is only the API and webhooks.
|
||||
serve_webapp = on
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+13
-13
@@ -68,23 +68,23 @@ is plain TypeScript, tested directly in Node.
|
||||
## Running the real service against this build
|
||||
|
||||
**`mock/server.py` is not the service.** The service is the Haskell
|
||||
executable `simplex-badge-service`, in `apps/simplex-badge-service/`, and
|
||||
executable `simplex-store-service`, in `apps/simplex-store-service/`, and
|
||||
`dist/` is the directory its listener serves. Nothing about the page changes
|
||||
between the two — the service matches this build, never the reverse.
|
||||
|
||||
```
|
||||
# 1. build the page. dist/ is the deploy tree: index.html, sw.js, assets/<hash>/
|
||||
cd apps/simplex-badge-service/web
|
||||
cd apps/simplex-store-service/web
|
||||
npm install && npm run build
|
||||
cd ../../..
|
||||
|
||||
# 2. configuration, from the committed example. Copy it IN
|
||||
# PLACE, beside the example: that path is the one .gitignore covers, and this
|
||||
# file is about to hold a real api_key and webhook_secret.
|
||||
cd apps/simplex-badge-service
|
||||
cp badge_service.ini.example badge_service.ini
|
||||
cd apps/simplex-store-service
|
||||
cp store_service.ini.example store_service.ini
|
||||
cd ../..
|
||||
# static_dir = ./apps/simplex-badge-service/web/dist <- the build above
|
||||
# static_dir = ./apps/simplex-store-service/web/dist <- the build above
|
||||
# [btcpay] = a real store's host, api_key, store_id and webhook_secret,
|
||||
# or delete the whole section to disable BTC and XMR (the provider-unavailable screen)
|
||||
# [issuer] = uncomment the section and put a real issuer secret in private_key,
|
||||
@@ -98,28 +98,28 @@ cd ../..
|
||||
# without a key it could sign a credential with. Put it in the ini's [issuer]
|
||||
# section, or pass --issuer-key-idx and --issuer-secret, which override it.
|
||||
# `simplex-chat badge keygen` prints a pair.
|
||||
cabal run simplex-badge-service -- \
|
||||
--service-config apps/simplex-badge-service/badge_service.ini
|
||||
cabal run simplex-store-service -- \
|
||||
--service-config apps/simplex-store-service/store_service.ini
|
||||
```
|
||||
|
||||
Then open the `[listener]` host and port — `http://127.0.0.1:8080/` as the
|
||||
example ships. `static_dir` is relative to the working directory, so run from
|
||||
the repository root or make it absolute.
|
||||
|
||||
**The executable is the whole badge service, not just this listener.**
|
||||
`badgeService` starts `simplexChatCore` unconditionally and the web listener
|
||||
**The executable is the whole store service, not just this listener.**
|
||||
`storeService` starts `simplexChatCore` unconditionally and the web listener
|
||||
is one lane beside the chat one, so a local run opens or creates a chat
|
||||
database (`printDbOpts` names the path at startup) and does agent network
|
||||
work. `--no-address` skips creating the service's contact address on first
|
||||
start, which a run of the web listener alone does not need.
|
||||
|
||||
*These three steps are read out of `Options.hs`, `Service.hs`, `Config.hs` and
|
||||
`badge_service.ini.example` rather than from a run — driving them end to end
|
||||
`store_service.ini.example` rather than from a run — driving them end to end
|
||||
needs a chat database and a real BTCPay store.*
|
||||
|
||||
What the mock cannot stand in for:
|
||||
|
||||
| | `mock/server.py` | `simplex-badge-service` |
|
||||
| | `mock/server.py` | `simplex-store-service` |
|
||||
|---|---|---|
|
||||
| Invoices | invented in memory | created at BTCPay over Greenfield |
|
||||
| Payment detected by | `POST /control/settle/:id`, an endpoint that exists nowhere else | a poller reading the provider, which is the only thing that carries authority |
|
||||
@@ -129,10 +129,10 @@ What the mock cannot stand in for:
|
||||
|
||||
The Haskell side's own end-to-end coverage of that lane — checkout, payment,
|
||||
polling, partial payment, expiry, late settlement and replay, all against a
|
||||
fake Greenfield — is `tests/Bots/BadgeService/WebTests.hs`:
|
||||
fake Greenfield — is `tests/Bots/StoreService/WebTests.hs`:
|
||||
|
||||
```
|
||||
cabal test --test-options='-m "/SimpleX badge service/"'
|
||||
cabal test --test-options='-m "/SimpleX store service/"'
|
||||
```
|
||||
|
||||
## Running the mock
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -311,7 +311,7 @@ def main():
|
||||
# so only a publishable key (pk_) is allowed.
|
||||
sys.exit("mock: STRIPE_PUBLISHABLE_KEY must be a publishable key (pk_...)")
|
||||
server = Server(("127.0.0.1", port), Handler)
|
||||
print(f"mock badge service on http://localhost:{port}", flush=True)
|
||||
print(f"mock store service on http://localhost:{port}", flush=True)
|
||||
print("stripe: " + (f"publishable key {STRIPE_PUBLISHABLE_KEY[:11]}… — the real card form"
|
||||
if STRIPE_PUBLISHABLE_KEY
|
||||
else "no STRIPE_PUBLISHABLE_KEY — the card path renders the development stand-in"),
|
||||
Generated
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 152 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user