Skip to content

Commit f92422e

Browse files
committed
Add payer metadata endpoint (#1013)
### TL;DR Added a new API endpoint to retrieve payer usage information, including spend and message counts, with hourly or daily granularity. ## Requires - xmtp/proto#287 ### What changed? - Added a new `GetPayerInfo` endpoint to the Metadata API service that allows querying payer usage data - Created a `PayerInfoFetcher` to retrieve payer information from the database - Added message count tracking to the `unsettled_usage` table via a new migration - Modified the protocol buffer definitions to support the new API endpoint - Updated the `dev/gen/protos` script to support configurable branch selection via `GEN_PROTO_BRANCH` environment variable ### How to test? 1. Run the database migrations to add the message count column 2. Use the new API endpoint with a payer address: ``` curl -X POST http://localhost:8080/mls/v2/metadata/get-payer-info \ -H "Content-Type: application/json" \ -d '{"payer_addresses": ["0x..."], "granularity": "PAYER_INFO_GRANULARITY_DAY"}' ``` 3. Run the comprehensive test suite in `payer_info_test.go` which covers various scenarios including: - Different time granularities (hourly/daily) - Time range filtering - Multiple originators - Edge cases and boundary conditions ### Why make this change? This change enables monitoring and reporting of payer usage metrics, allowing both the service operator and payers themselves to track message volume and spending over time. The granular data can be used for billing, analytics, and capacity planning purposes.
1 parent 09466d1 commit f92422e

18 files changed

Lines changed: 1247 additions & 35 deletions

dev/gen/protos

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ script_dir=$(dirname "$(realpath "$0")")
55
repo_root=$(realpath "${script_dir}/../../")
66
cd "${repo_root}"
77

8+
GEN_PROTO_BRANCH="${GEN_PROTO_BRANCH:-main}"
9+
810
rm -rf pkg/proto/**/*.pb.go pkg/proto/**/*.pb.gw.go pkg/proto/**/*.swagger.json
9-
if ! go tool -modfile=tools/go.mod buf generate https://github.com/xmtp/proto.git#subdir=proto,branch=main; then
11+
if ! go tool -modfile=tools/go.mod buf generate "https://github.com/xmtp/proto.git#subdir=proto,branch=${GEN_PROTO_BRANCH}"; then
1012
echo "Failed to generate protobuf definitions"
1113
exit 1
1214
fi

pkg/api/metadata/payer_info.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package metadata
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
7+
"github.com/xmtp/xmtpd/pkg/db/queries"
8+
"github.com/xmtp/xmtpd/pkg/proto/xmtpv4/metadata_api"
9+
)
10+
11+
type PayerInfoGroupBy string
12+
13+
const (
14+
PayerInfoGroupByHour PayerInfoGroupBy = "hour"
15+
PayerInfoGroupByDay PayerInfoGroupBy = "day"
16+
)
17+
18+
type IPayerInfoFetcher interface {
19+
GetPayerByAddress(ctx context.Context, address string) (int32, error)
20+
GetPayerInfo(
21+
ctx context.Context,
22+
payerID int32,
23+
groupBy PayerInfoGroupBy,
24+
) (*metadata_api.GetPayerInfoResponse_PayerInfo, error)
25+
}
26+
27+
type PayerInfoFetcher struct {
28+
queries *queries.Queries
29+
}
30+
31+
func NewPayerInfoFetcher(db *sql.DB) *PayerInfoFetcher {
32+
return &PayerInfoFetcher{
33+
queries: queries.New(db),
34+
}
35+
}
36+
37+
// Gets the total spend and message count for a payer between the two timestamps, grouped by the appropriate granularity.
38+
func (f *PayerInfoFetcher) GetPayerInfo(
39+
ctx context.Context,
40+
payerID int32,
41+
groupBy PayerInfoGroupBy,
42+
) (*metadata_api.GetPayerInfoResponse_PayerInfo, error) {
43+
result, err := f.queries.GetPayerInfoReport(ctx, queries.GetPayerInfoReportParams{
44+
GroupBy: string(groupBy),
45+
PayerID: payerID,
46+
})
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
payerInfo := &metadata_api.GetPayerInfoResponse_PayerInfo{
52+
PeriodSummaries: make([]*metadata_api.GetPayerInfoResponse_PeriodSummary, 0),
53+
}
54+
55+
for _, row := range result {
56+
periodSummary := &metadata_api.GetPayerInfoResponse_PeriodSummary{
57+
AmountSpentPicodollars: uint64(row.TotalSpendPicodollars),
58+
NumMessages: uint64(row.TotalMessageCount),
59+
PeriodStartUnixSeconds: uint64(row.TimePeriod),
60+
}
61+
62+
payerInfo.PeriodSummaries = append(payerInfo.PeriodSummaries, periodSummary)
63+
}
64+
65+
return payerInfo, nil
66+
}
67+
68+
// GetPayerByAddress looks up a payer ID by address
69+
func (f *PayerInfoFetcher) GetPayerByAddress(ctx context.Context, address string) (int32, error) {
70+
return f.queries.GetPayerByAddress(ctx, address)
71+
}

0 commit comments

Comments
 (0)