feat(go): M5.1 — hand-author /api/* wire types + JSON Schemas
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Add internal/web/api package with Go structs for every /api/X route:
AdultsResponse, JuniorsResponse, PaymentsResponse, VersionResponse.
All fields carry explicit json: tags matching the Python view-model keys.
Key design choices:
- member_data / month_labels / raw_payments are nested objects (not
the pre-serialised JSON strings used in Jinja templates)
- Expected{Value int; Unknown bool} with custom MarshalJSON emits int
or the string "?" for junior single-attendance months
- RawTransaction covers the full 11-column payments sheet row
schemagen_test.go reflects all four response types via
github.com/invopop/jsonschema and golden-compares against committed
schemas in tests/fixtures/api-schema/. The JSONSchema() method on
Expected lives in the test file so the prod binary has no jsonschema
dependency.
Closes M5.1 in docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ module fuj-management/go
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/invopop/jsonschema v0.14.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/text v0.36.0
|
||||
google.golang.org/api v0.278.0
|
||||
@@ -12,6 +13,8 @@ require (
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
@@ -20,11 +23,13 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
|
||||
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
|
||||
golang.org/x/crypto v0.50.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
|
||||
10
go/go.sum
10
go/go.sum
@@ -4,6 +4,10 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
|
||||
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -27,6 +31,10 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5Ugt
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
|
||||
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
|
||||
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
|
||||
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
|
||||
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
|
||||
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
@@ -45,6 +53,8 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
|
||||
42
go/internal/web/api/adults.go
Normal file
42
go/internal/web/api/adults.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package api
|
||||
|
||||
// AdultsMonthData is the reconciled ledger for one adult member in one month.
|
||||
// Keys match Python's result["members"][name]["months"][YYYY-MM].
|
||||
type AdultsMonthData struct {
|
||||
Expected int `json:"expected"`
|
||||
OriginalExpected int `json:"original_expected"`
|
||||
AttendanceCount int `json:"attendance_count"`
|
||||
Exception *ExceptionData `json:"exception"`
|
||||
Paid float64 `json:"paid"` // float: proportional allocator may produce fractional CZK
|
||||
Transactions []MemberTxEntry `json:"transactions"`
|
||||
}
|
||||
|
||||
// AdultsMemberData is the reconciled ledger for one adult member.
|
||||
// Keys match Python's result["members"][name].
|
||||
type AdultsMemberData struct {
|
||||
Tier string `json:"tier"`
|
||||
Months map[string]AdultsMonthData `json:"months"` // YYYY-MM → month data
|
||||
OtherTransactions []MemberOtherEntry `json:"other_transactions"`
|
||||
TotalBalance int `json:"total_balance"`
|
||||
}
|
||||
|
||||
// AdultsResponse is the JSON contract for GET /api/adults.
|
||||
// MemberData, MonthLabels, and RawPayments correspond to the Python view-model
|
||||
// fields member_data, month_labels_json, and raw_payments_json respectively,
|
||||
// but as nested objects rather than pre-serialised JSON strings.
|
||||
type AdultsResponse struct {
|
||||
Months []string `json:"months"` // display labels
|
||||
RawMonths []string `json:"raw_months"` // "YYYY-MM"
|
||||
Results []MemberRow `json:"results"`
|
||||
Totals []TotalCell `json:"totals"`
|
||||
MemberData map[string]AdultsMemberData `json:"member_data"` // name → ledger
|
||||
MonthLabels map[string]string `json:"month_labels"` // YYYY-MM → display label
|
||||
RawPayments map[string][]RawTransaction `json:"raw_payments"` // name → raw sheet rows
|
||||
Credits []Credit `json:"credits"`
|
||||
Debts []Credit `json:"debts"`
|
||||
Unmatched []RawTransaction `json:"unmatched"`
|
||||
AttendanceURL string `json:"attendance_url"`
|
||||
PaymentsURL string `json:"payments_url"`
|
||||
BankAccount string `json:"bank_account"`
|
||||
CurrentMonth string `json:"current_month"`
|
||||
}
|
||||
41
go/internal/web/api/juniors.go
Normal file
41
go/internal/web/api/juniors.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package api
|
||||
|
||||
// JuniorsMonthData is the reconciled ledger for one junior member in one month.
|
||||
// expected and original_expected may be the "?" sentinel (single-attendance month
|
||||
// requiring manual review); they are carried via the Expected type.
|
||||
type JuniorsMonthData struct {
|
||||
Expected Expected `json:"expected"`
|
||||
OriginalExpected Expected `json:"original_expected"`
|
||||
AttendanceCount int `json:"attendance_count"`
|
||||
Exception *ExceptionData `json:"exception"`
|
||||
Paid float64 `json:"paid"`
|
||||
Transactions []MemberTxEntry `json:"transactions"`
|
||||
}
|
||||
|
||||
// JuniorsMemberData is the reconciled ledger for one junior member.
|
||||
type JuniorsMemberData struct {
|
||||
Tier string `json:"tier"`
|
||||
Months map[string]JuniorsMonthData `json:"months"`
|
||||
OtherTransactions []MemberOtherEntry `json:"other_transactions"`
|
||||
TotalBalance int `json:"total_balance"`
|
||||
}
|
||||
|
||||
// JuniorsResponse is the JSON contract for GET /api/juniors.
|
||||
// Same outer shape as AdultsResponse; differs in that member_data carries
|
||||
// Expected (int or "?") for expected/original_expected fields.
|
||||
type JuniorsResponse struct {
|
||||
Months []string `json:"months"`
|
||||
RawMonths []string `json:"raw_months"`
|
||||
Results []MemberRow `json:"results"`
|
||||
Totals []TotalCell `json:"totals"`
|
||||
MemberData map[string]JuniorsMemberData `json:"member_data"`
|
||||
MonthLabels map[string]string `json:"month_labels"`
|
||||
RawPayments map[string][]RawTransaction `json:"raw_payments"`
|
||||
Credits []Credit `json:"credits"`
|
||||
Debts []Credit `json:"debts"`
|
||||
Unmatched []RawTransaction `json:"unmatched"`
|
||||
AttendanceURL string `json:"attendance_url"`
|
||||
PaymentsURL string `json:"payments_url"`
|
||||
BankAccount string `json:"bank_account"`
|
||||
CurrentMonth string `json:"current_month"`
|
||||
}
|
||||
9
go/internal/web/api/payments.go
Normal file
9
go/internal/web/api/payments.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package api
|
||||
|
||||
// PaymentsResponse is the JSON contract for GET /api/payments.
|
||||
type PaymentsResponse struct {
|
||||
GroupedPayments map[string][]RawTransaction `json:"grouped_payments"` // person name → rows
|
||||
SortedPeople []string `json:"sorted_people"`
|
||||
AttendanceURL string `json:"attendance_url"`
|
||||
PaymentsURL string `json:"payments_url"`
|
||||
}
|
||||
81
go/internal/web/api/schemagen_test.go
Normal file
81
go/internal/web/api/schemagen_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package api
|
||||
|
||||
// schemagen_test.go generates and golden-compares JSON Schema files for every
|
||||
// /api/X response type.
|
||||
//
|
||||
// Normal run (CI): go test ./internal/web/api/... — asserts schemas match committed files.
|
||||
// Regenerate: go test -run TestGenerateSchemas -update ./internal/web/api/...
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/invopop/jsonschema"
|
||||
)
|
||||
|
||||
var updateFlag = flag.Bool("update", false, "overwrite api-schema fixture files with freshly generated schemas")
|
||||
|
||||
// JSONSchema makes Expected self-describing for the reflector at test time.
|
||||
// The method is in a test file and is not compiled into production binaries.
|
||||
// It emits oneOf [integer, "?"] to match the custom MarshalJSON behaviour.
|
||||
func (Expected) JSONSchema() *jsonschema.Schema {
|
||||
return &jsonschema.Schema{
|
||||
OneOf: []*jsonschema.Schema{
|
||||
{Type: "integer"},
|
||||
{Enum: []any{"?"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSchemas(t *testing.T) {
|
||||
r := &jsonschema.Reflector{
|
||||
AllowAdditionalProperties: false,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
val any
|
||||
}{
|
||||
{"adults", &AdultsResponse{}},
|
||||
{"juniors", &JuniorsResponse{}},
|
||||
{"payments", &PaymentsResponse{}},
|
||||
{"version", &VersionResponse{}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
schema := r.Reflect(tc.val)
|
||||
|
||||
got, err := json.MarshalIndent(schema, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("marshal schema: %v", err)
|
||||
}
|
||||
got = append(got, '\n')
|
||||
|
||||
// Path: go/internal/web/api/ → ../../.. → go/ → tests/fixtures/api-schema/
|
||||
path := filepath.Join("..", "..", "..", "tests", "fixtures", "api-schema", tc.name+".schema.json")
|
||||
|
||||
if *updateFlag {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(path, got, 0o644); err != nil {
|
||||
t.Fatalf("write schema: %v", err)
|
||||
}
|
||||
t.Logf("wrote %s", path)
|
||||
return
|
||||
}
|
||||
|
||||
want, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read fixture %s: %v (re-run with -update to generate)", path, err)
|
||||
}
|
||||
if string(got) != string(want) {
|
||||
t.Errorf("schema mismatch for %s; re-run with -update to regenerate", tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
122
go/internal/web/api/types.go
Normal file
122
go/internal/web/api/types.go
Normal file
@@ -0,0 +1,122 @@
|
||||
// Package api defines wire types for the JSON API contract (/api/...).
|
||||
// These structs have explicit json: tags matching the Python view-model dict
|
||||
// keys so that M5 parity tests can do byte-equal comparison between backends.
|
||||
//
|
||||
// The three Python template-only JSON-string fields (member_data,
|
||||
// month_labels_json, raw_payments_json) are represented here as nested objects;
|
||||
// the Python /api/X shadow endpoint strips the json.dumps wrappers before
|
||||
// serialising.
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Expected holds a junior fee expectation: either a concrete integer or the
|
||||
// "?" sentinel (single-attendance month requiring manual review).
|
||||
// MarshalJSON emits the integer or the JSON string "?".
|
||||
type Expected struct {
|
||||
Value int
|
||||
Unknown bool
|
||||
}
|
||||
|
||||
func (e Expected) MarshalJSON() ([]byte, error) {
|
||||
if e.Unknown {
|
||||
return []byte(`"?"`), nil
|
||||
}
|
||||
return json.Marshal(e.Value)
|
||||
}
|
||||
|
||||
func (e *Expected) UnmarshalJSON(data []byte) error {
|
||||
if len(data) > 0 && data[0] == '"' {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "?" {
|
||||
e.Unknown = true
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("api.Expected: unexpected string %q", s)
|
||||
}
|
||||
e.Unknown = false
|
||||
return json.Unmarshal(data, &e.Value)
|
||||
}
|
||||
|
||||
// ExceptionData is a manual fee override for one member in one month.
|
||||
type ExceptionData struct {
|
||||
Amount int `json:"amount"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
// MemberTxEntry is one payment allocation to a member+month, as stored in
|
||||
// member_data.months[YYYY-MM].transactions.
|
||||
type MemberTxEntry struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Date string `json:"date"`
|
||||
Sender string `json:"sender"`
|
||||
Message string `json:"message"`
|
||||
Confidence string `json:"confidence"`
|
||||
}
|
||||
|
||||
// MemberOtherEntry is an "other:…" purpose payment allocated to a member.
|
||||
type MemberOtherEntry struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Date string `json:"date"`
|
||||
Sender string `json:"sender"`
|
||||
Message string `json:"message"`
|
||||
Purpose string `json:"purpose"`
|
||||
Confidence string `json:"confidence"`
|
||||
}
|
||||
|
||||
// RawTransaction is a full payments-sheet row.
|
||||
// Used for unmatched transactions and raw_payments groupings.
|
||||
// Columns match the sheet layout: Date|Amount|manual fix|Person|Purpose|
|
||||
// Inferred Amount|Sender|VS|Message|Bank ID|Sync ID.
|
||||
type RawTransaction struct {
|
||||
Date string `json:"date"`
|
||||
Amount float64 `json:"amount"`
|
||||
ManualFix string `json:"manual_fix"`
|
||||
Person string `json:"person"`
|
||||
Purpose string `json:"purpose"`
|
||||
InferredAmount float64 `json:"inferred_amount"`
|
||||
Sender string `json:"sender"`
|
||||
VS string `json:"vs"`
|
||||
Message string `json:"message"`
|
||||
BankID string `json:"bank_id"`
|
||||
SyncID string `json:"sync_id"`
|
||||
}
|
||||
|
||||
// MonthCell is one cell in a member's month column on the dashboard.
|
||||
type MonthCell struct {
|
||||
Text string `json:"text"`
|
||||
Overridden bool `json:"overridden"`
|
||||
Status string `json:"status"` // "empty"|"ok"|"partial"|"unpaid"|"surplus"
|
||||
Amount int `json:"amount"`
|
||||
Month string `json:"month"` // display label, e.g. "Apr+May 2025"
|
||||
RawMonth string `json:"raw_month"` // "YYYY-MM"
|
||||
Tooltip string `json:"tooltip"`
|
||||
}
|
||||
|
||||
// TotalCell is one cell in the monthly totals row.
|
||||
type TotalCell struct {
|
||||
Text string `json:"text"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// MemberRow is one member's summary row in the dashboard results table.
|
||||
type MemberRow struct {
|
||||
Name string `json:"name"`
|
||||
Months []MonthCell `json:"months"`
|
||||
Balance int `json:"balance"`
|
||||
UnpaidPeriods string `json:"unpaid_periods"`
|
||||
RawUnpaidPeriods string `json:"raw_unpaid_periods"`
|
||||
PayableAmount int `json:"payable_amount"`
|
||||
}
|
||||
|
||||
// Credit is one entry in the credits or debts lists.
|
||||
type Credit struct {
|
||||
Name string `json:"name"`
|
||||
Amount int `json:"amount"`
|
||||
}
|
||||
9
go/internal/web/api/version.go
Normal file
9
go/internal/web/api/version.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package api
|
||||
|
||||
// VersionResponse is the JSON contract for GET /api/version.
|
||||
// Keys match Python's BUILD_META dict (see app.py).
|
||||
type VersionResponse struct {
|
||||
Tag string `json:"tag"`
|
||||
Commit string `json:"commit"`
|
||||
BuildDate string `json:"build_date"`
|
||||
}
|
||||
399
go/tests/fixtures/api-schema/adults.schema.json
vendored
Normal file
399
go/tests/fixtures/api-schema/adults.schema.json
vendored
Normal file
@@ -0,0 +1,399 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$defs/AdultsResponse",
|
||||
"$defs": {
|
||||
"AdultsMemberData": {
|
||||
"properties": {
|
||||
"tier": {
|
||||
"type": "string"
|
||||
},
|
||||
"months": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/AdultsMonthData"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"other_transactions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MemberOtherEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"total_balance": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tier",
|
||||
"months",
|
||||
"other_transactions",
|
||||
"total_balance"
|
||||
]
|
||||
},
|
||||
"AdultsMonthData": {
|
||||
"properties": {
|
||||
"expected": {
|
||||
"type": "integer"
|
||||
},
|
||||
"original_expected": {
|
||||
"type": "integer"
|
||||
},
|
||||
"attendance_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"exception": {
|
||||
"$ref": "#/$defs/ExceptionData"
|
||||
},
|
||||
"paid": {
|
||||
"type": "number"
|
||||
},
|
||||
"transactions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MemberTxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"expected",
|
||||
"original_expected",
|
||||
"attendance_count",
|
||||
"exception",
|
||||
"paid",
|
||||
"transactions"
|
||||
]
|
||||
},
|
||||
"AdultsResponse": {
|
||||
"properties": {
|
||||
"months": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"raw_months": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"results": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MemberRow"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"totals": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/TotalCell"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"member_data": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/AdultsMemberData"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"month_labels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"raw_payments": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RawTransaction"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"credits": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Credit"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"debts": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Credit"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"unmatched": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RawTransaction"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"attendance_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"payments_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_account": {
|
||||
"type": "string"
|
||||
},
|
||||
"current_month": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"months",
|
||||
"raw_months",
|
||||
"results",
|
||||
"totals",
|
||||
"member_data",
|
||||
"month_labels",
|
||||
"raw_payments",
|
||||
"credits",
|
||||
"debts",
|
||||
"unmatched",
|
||||
"attendance_url",
|
||||
"payments_url",
|
||||
"bank_account",
|
||||
"current_month"
|
||||
]
|
||||
},
|
||||
"Credit": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"amount"
|
||||
]
|
||||
},
|
||||
"ExceptionData": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"amount",
|
||||
"note"
|
||||
]
|
||||
},
|
||||
"MemberOtherEntry": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"confidence": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"amount",
|
||||
"date",
|
||||
"sender",
|
||||
"message",
|
||||
"purpose",
|
||||
"confidence"
|
||||
]
|
||||
},
|
||||
"MemberRow": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"months": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MonthCell"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"balance": {
|
||||
"type": "integer"
|
||||
},
|
||||
"unpaid_periods": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_unpaid_periods": {
|
||||
"type": "string"
|
||||
},
|
||||
"payable_amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"months",
|
||||
"balance",
|
||||
"unpaid_periods",
|
||||
"raw_unpaid_periods",
|
||||
"payable_amount"
|
||||
]
|
||||
},
|
||||
"MemberTxEntry": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"confidence": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"amount",
|
||||
"date",
|
||||
"sender",
|
||||
"message",
|
||||
"confidence"
|
||||
]
|
||||
},
|
||||
"MonthCell": {
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"overridden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"month": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_month": {
|
||||
"type": "string"
|
||||
},
|
||||
"tooltip": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text",
|
||||
"overridden",
|
||||
"status",
|
||||
"amount",
|
||||
"month",
|
||||
"raw_month",
|
||||
"tooltip"
|
||||
]
|
||||
},
|
||||
"RawTransaction": {
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"manual_fix": {
|
||||
"type": "string"
|
||||
},
|
||||
"person": {
|
||||
"type": "string"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"inferred_amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"vs": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"sync_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"date",
|
||||
"amount",
|
||||
"manual_fix",
|
||||
"person",
|
||||
"purpose",
|
||||
"inferred_amount",
|
||||
"sender",
|
||||
"vs",
|
||||
"message",
|
||||
"bank_id",
|
||||
"sync_id"
|
||||
]
|
||||
},
|
||||
"TotalCell": {
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text",
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
411
go/tests/fixtures/api-schema/juniors.schema.json
vendored
Normal file
411
go/tests/fixtures/api-schema/juniors.schema.json
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$defs/JuniorsResponse",
|
||||
"$defs": {
|
||||
"Credit": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"amount"
|
||||
]
|
||||
},
|
||||
"ExceptionData": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"amount",
|
||||
"note"
|
||||
]
|
||||
},
|
||||
"Expected": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"?"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"JuniorsMemberData": {
|
||||
"properties": {
|
||||
"tier": {
|
||||
"type": "string"
|
||||
},
|
||||
"months": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/JuniorsMonthData"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"other_transactions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MemberOtherEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"total_balance": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tier",
|
||||
"months",
|
||||
"other_transactions",
|
||||
"total_balance"
|
||||
]
|
||||
},
|
||||
"JuniorsMonthData": {
|
||||
"properties": {
|
||||
"expected": {
|
||||
"$ref": "#/$defs/Expected"
|
||||
},
|
||||
"original_expected": {
|
||||
"$ref": "#/$defs/Expected"
|
||||
},
|
||||
"attendance_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"exception": {
|
||||
"$ref": "#/$defs/ExceptionData"
|
||||
},
|
||||
"paid": {
|
||||
"type": "number"
|
||||
},
|
||||
"transactions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MemberTxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"expected",
|
||||
"original_expected",
|
||||
"attendance_count",
|
||||
"exception",
|
||||
"paid",
|
||||
"transactions"
|
||||
]
|
||||
},
|
||||
"JuniorsResponse": {
|
||||
"properties": {
|
||||
"months": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"raw_months": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"results": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MemberRow"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"totals": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/TotalCell"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"member_data": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/JuniorsMemberData"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"month_labels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"raw_payments": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RawTransaction"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"credits": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Credit"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"debts": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Credit"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"unmatched": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RawTransaction"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"attendance_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"payments_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_account": {
|
||||
"type": "string"
|
||||
},
|
||||
"current_month": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"months",
|
||||
"raw_months",
|
||||
"results",
|
||||
"totals",
|
||||
"member_data",
|
||||
"month_labels",
|
||||
"raw_payments",
|
||||
"credits",
|
||||
"debts",
|
||||
"unmatched",
|
||||
"attendance_url",
|
||||
"payments_url",
|
||||
"bank_account",
|
||||
"current_month"
|
||||
]
|
||||
},
|
||||
"MemberOtherEntry": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"confidence": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"amount",
|
||||
"date",
|
||||
"sender",
|
||||
"message",
|
||||
"purpose",
|
||||
"confidence"
|
||||
]
|
||||
},
|
||||
"MemberRow": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"months": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/MonthCell"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"balance": {
|
||||
"type": "integer"
|
||||
},
|
||||
"unpaid_periods": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_unpaid_periods": {
|
||||
"type": "string"
|
||||
},
|
||||
"payable_amount": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"months",
|
||||
"balance",
|
||||
"unpaid_periods",
|
||||
"raw_unpaid_periods",
|
||||
"payable_amount"
|
||||
]
|
||||
},
|
||||
"MemberTxEntry": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"confidence": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"amount",
|
||||
"date",
|
||||
"sender",
|
||||
"message",
|
||||
"confidence"
|
||||
]
|
||||
},
|
||||
"MonthCell": {
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"overridden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"month": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_month": {
|
||||
"type": "string"
|
||||
},
|
||||
"tooltip": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text",
|
||||
"overridden",
|
||||
"status",
|
||||
"amount",
|
||||
"month",
|
||||
"raw_month",
|
||||
"tooltip"
|
||||
]
|
||||
},
|
||||
"RawTransaction": {
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"manual_fix": {
|
||||
"type": "string"
|
||||
},
|
||||
"person": {
|
||||
"type": "string"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"inferred_amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"vs": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"sync_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"date",
|
||||
"amount",
|
||||
"manual_fix",
|
||||
"person",
|
||||
"purpose",
|
||||
"inferred_amount",
|
||||
"sender",
|
||||
"vs",
|
||||
"message",
|
||||
"bank_id",
|
||||
"sync_id"
|
||||
]
|
||||
},
|
||||
"TotalCell": {
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text",
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
91
go/tests/fixtures/api-schema/payments.schema.json
vendored
Normal file
91
go/tests/fixtures/api-schema/payments.schema.json
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$defs/PaymentsResponse",
|
||||
"$defs": {
|
||||
"PaymentsResponse": {
|
||||
"properties": {
|
||||
"grouped_payments": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/RawTransaction"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"sorted_people": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"attendance_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"payments_url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"grouped_payments",
|
||||
"sorted_people",
|
||||
"attendance_url",
|
||||
"payments_url"
|
||||
]
|
||||
},
|
||||
"RawTransaction": {
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"manual_fix": {
|
||||
"type": "string"
|
||||
},
|
||||
"person": {
|
||||
"type": "string"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"inferred_amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"sender": {
|
||||
"type": "string"
|
||||
},
|
||||
"vs": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"sync_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"date",
|
||||
"amount",
|
||||
"manual_fix",
|
||||
"person",
|
||||
"purpose",
|
||||
"inferred_amount",
|
||||
"sender",
|
||||
"vs",
|
||||
"message",
|
||||
"bank_id",
|
||||
"sync_id"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
26
go/tests/fixtures/api-schema/version.schema.json
vendored
Normal file
26
go/tests/fixtures/api-schema/version.schema.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$ref": "#/$defs/VersionResponse",
|
||||
"$defs": {
|
||||
"VersionResponse": {
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string"
|
||||
},
|
||||
"commit": {
|
||||
"type": "string"
|
||||
},
|
||||
"build_date": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": [
|
||||
"tag",
|
||||
"commit",
|
||||
"build_date"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user