diff --git a/api/api.go b/api/api.go
index e864cfe..7049e3a 100644
--- a/api/api.go
+++ b/api/api.go
@@ -23,6 +23,8 @@ import (
"nuts-foundation/nuts-monitor/client"
"nuts-foundation/nuts-monitor/client/diagnostics"
"nuts-foundation/nuts-monitor/config"
+ "nuts-foundation/nuts-monitor/data"
+ "time"
)
var _ StrictServerInterface = (*Wrapper)(nil)
@@ -33,8 +35,9 @@ const (
)
type Wrapper struct {
- Config config.Config
- Client client.HTTPClient
+ Config config.Config
+ Client client.HTTPClient
+ DataStore *data.Store
}
func (w Wrapper) Diagnostics(ctx context.Context, _ DiagnosticsRequestObject) (DiagnosticsResponseObject, error) {
@@ -97,3 +100,32 @@ func (w Wrapper) NetworkTopology(ctx context.Context, _ NetworkTopologyRequestOb
return NetworkTopology200JSONResponse(networkTopology), nil
}
+
+func (w Wrapper) GetWebTransactionsAggregated(ctx context.Context, _ GetWebTransactionsAggregatedRequestObject) (GetWebTransactionsAggregatedResponseObject, error) {
+ // get data from the store
+ dataPoints := w.DataStore.GetTransactions()
+
+ // convert the data points to the response object
+ response := AggregatedTransactions{}
+ // loop over the 3 categories of data points
+ // for each category, loop over the data points and add them to the correct category in the response object
+ for _, dp := range dataPoints[0] {
+ response.Hourly = append(response.Hourly, toDataPoint(dp))
+ }
+ for _, dp := range dataPoints[1] {
+ response.Daily = append(response.Daily, toDataPoint(dp))
+ }
+ for _, dp := range dataPoints[2] {
+ response.Monthly = append(response.Monthly, toDataPoint(dp))
+ }
+
+ return GetWebTransactionsAggregated200JSONResponse(response), nil
+}
+
+func toDataPoint(dp data.DataPoint) DataPoint {
+ return DataPoint{
+ Timestamp: int(dp.Timestamp.Unix()),
+ Label: dp.Timestamp.Format(time.RFC3339),
+ Value: int(dp.Count),
+ }
+}
diff --git a/api/api.yaml b/api/api.yaml
index 2281a47..19a345f 100644
--- a/api/api.yaml
+++ b/api/api.yaml
@@ -1,6 +1,6 @@
openapi: 3.0.0
info:
- title: Nuts Registry Admin API
+ title: Nuts Monitor API
version: 1.0.0
paths:
@@ -44,8 +44,47 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/NetworkTopology"
+ /web/transactions/aggregated:
+ get:
+ summary: "Returns the transactions aggregated by time"
+ description: >
+ Returns the transactions aggregated by time. It contains three sets of data points:
+ - an interval of 1 hour with a resolution of 1 minute
+ - an interval of 1 day with a resolution of 1 hour
+ - an interval of 1 month with a resolution of 1 day
+ operationId: aggregatedTransactions
+ responses:
+ 200:
+ description: "Aggregated transactions data"
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AggregatedTransactions"
components:
schemas:
+ AggregatedTransactions:
+ type: object
+ description: "Aggregated transactions data"
+ required:
+ - hourly
+ - daily
+ - monthly
+ properties:
+ hourly:
+ type: array
+ description: "Aggregated transactions data for the last hour"
+ items:
+ $ref: "#/components/schemas/DataPoint"
+ daily:
+ type: array
+ description: "Aggregated transactions data for the last day"
+ items:
+ $ref: "#/components/schemas/DataPoint"
+ monthly:
+ type: array
+ description: "Aggregated transactions data for the last month"
+ items:
+ $ref: "#/components/schemas/DataPoint"
CheckHealthResponse:
required:
- status
@@ -59,6 +98,23 @@ components:
description: Map of the performed health checks and their results.
additionalProperties:
$ref: "#/components/schemas/HealthCheckResult"
+ DataPoint:
+ type: object
+ description: "Data point"
+ required:
+ - timestamp
+ - label
+ - value
+ properties:
+ timestamp:
+ type: integer
+ description: "time of the data point formatted as unix timestamp"
+ label:
+ type: string
+ description: "time of the data point formatted as RFC3339"
+ value:
+ type: integer
+ description: "number of transactions between the given timestamp and the next timestamp"
Diagnostics:
required:
- network
diff --git a/api/generated.go b/api/generated.go
index c596b89..3f7928f 100644
--- a/api/generated.go
+++ b/api/generated.go
@@ -12,27 +12,33 @@ import (
"github.com/labstack/echo/v4"
)
-// ConnectedPeer information on a single connected peer
-type ConnectedPeer struct {
- // Address domain or IP address of connected node
- Address string `json:"address"`
+// AggregatedTransactions Aggregated transactions data
+type AggregatedTransactions struct {
+ // Daily Aggregated transactions data for the last day
+ Daily []DataPoint `json:"daily"`
- // Authenticated True if NodeDID and certificate are correctly configured
- Authenticated bool `json:"authenticated"`
+ // Hourly Aggregated transactions data for the last hour
+ Hourly []DataPoint `json:"hourly"`
- // Id PeerID aka UUID of a node
- Id string `json:"id"`
+ // Monthly Aggregated transactions data for the last month
+ Monthly []DataPoint `json:"monthly"`
+}
+
+// DataPoint Data point
+type DataPoint struct {
+ // Label time of the data point formatted as RFC3339
+ Label string `json:"label"`
+
+ // Timestamp time of the data point formatted as unix timestamp
+ Timestamp int `json:"timestamp"`
- // Nodedid NodeDID if connection is authenticated
- Nodedid *string `json:"nodedid,omitempty"`
+ // Value number of transactions at the given timestamp
+ Value int `json:"value"`
}
// Network network and connection diagnostics
type Network struct {
Connections struct {
- // ConnectedPeers information on a single connected peer
- ConnectedPeers ConnectedPeer `json:"connected_peers"`
-
// ConnectedPeersCount number of peers connected
ConnectedPeersCount int `json:"connected_peers_count"`
@@ -122,6 +128,9 @@ type ServerInterface interface {
// Returns the network as a graph model
// (GET /web/network_topology)
NetworkTopology(ctx echo.Context) error
+ // Returns the transactions aggregated by time
+ // (GET /web/transactions/aggregated)
+ GetWebTransactionsAggregated(ctx echo.Context) error
}
// ServerInterfaceWrapper converts echo contexts to parameters.
@@ -156,6 +165,15 @@ func (w *ServerInterfaceWrapper) NetworkTopology(ctx echo.Context) error {
return err
}
+// GetWebTransactionsAggregated converts echo context to params.
+func (w *ServerInterfaceWrapper) GetWebTransactionsAggregated(ctx echo.Context) error {
+ var err error
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.GetWebTransactionsAggregated(ctx)
+ return err
+}
+
// This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration
@@ -187,6 +205,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/health", wrapper.CheckHealth)
router.GET(baseURL+"/web/diagnostics", wrapper.Diagnostics)
router.GET(baseURL+"/web/network_topology", wrapper.NetworkTopology)
+ router.GET(baseURL+"/web/transactions/aggregated", wrapper.GetWebTransactionsAggregated)
}
@@ -247,6 +266,22 @@ func (response NetworkTopology200JSONResponse) VisitNetworkTopologyResponse(w ht
return json.NewEncoder(w).Encode(response)
}
+type GetWebTransactionsAggregatedRequestObject struct {
+}
+
+type GetWebTransactionsAggregatedResponseObject interface {
+ VisitGetWebTransactionsAggregatedResponse(w http.ResponseWriter) error
+}
+
+type GetWebTransactionsAggregated200JSONResponse AggregatedTransactions
+
+func (response GetWebTransactionsAggregated200JSONResponse) VisitGetWebTransactionsAggregatedResponse(w http.ResponseWriter) error {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(200)
+
+ return json.NewEncoder(w).Encode(response)
+}
+
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
// More elaborate health check to conform the app is (probably) functioning correctly
@@ -258,6 +293,9 @@ type StrictServerInterface interface {
// Returns the network as a graph model
// (GET /web/network_topology)
NetworkTopology(ctx context.Context, request NetworkTopologyRequestObject) (NetworkTopologyResponseObject, error)
+ // Returns the transactions aggregated by time
+ // (GET /web/transactions/aggregated)
+ GetWebTransactionsAggregated(ctx context.Context, request GetWebTransactionsAggregatedRequestObject) (GetWebTransactionsAggregatedResponseObject, error)
}
type StrictHandlerFunc func(ctx echo.Context, args interface{}) (interface{}, error)
@@ -341,3 +379,26 @@ func (sh *strictHandler) NetworkTopology(ctx echo.Context) error {
}
return nil
}
+
+// GetWebTransactionsAggregated operation middleware
+func (sh *strictHandler) GetWebTransactionsAggregated(ctx echo.Context) error {
+ var request GetWebTransactionsAggregatedRequestObject
+
+ handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
+ return sh.ssi.GetWebTransactionsAggregated(ctx.Request().Context(), request.(GetWebTransactionsAggregatedRequestObject))
+ }
+ for _, middleware := range sh.middlewares {
+ handler = middleware(handler, "GetWebTransactionsAggregated")
+ }
+
+ response, err := handler(ctx, request)
+
+ if err != nil {
+ return err
+ } else if validResponse, ok := response.(GetWebTransactionsAggregatedResponseObject); ok {
+ return validResponse.VisitGetWebTransactionsAggregatedResponse(ctx.Response())
+ } else if response != nil {
+ return fmt.Errorf("Unexpected response type: %T", response)
+ }
+ return nil
+}
diff --git a/client/client.go b/client/client.go
index aef5319..5b4a6ba 100644
--- a/client/client.go
+++ b/client/client.go
@@ -150,3 +150,27 @@ func (hb HTTPClient) DIDDocument(ctx context.Context, did string) (*vdr.DIDResol
}
return nil, fmt.Errorf("received incorrect response from node: %s", string(result.Body))
}
+
+// ListTransactions returns transactions in a certain range according to LC value
+func (hb HTTPClient) ListTransactions(ctx context.Context, start int, end int) ([]string, error) {
+ var transactions []string
+
+ response, err := hb.networkClient().ListTransactions(ctx, &network.ListTransactionsParams{
+ Start: &start,
+ End: &end,
+ })
+ if err != nil {
+ return transactions, err
+ }
+ if err := TestResponseCode(http.StatusOK, response); err != nil {
+ return transactions, err
+ }
+ parsedResponse, err := network.ParseListTransactionsResponse(response)
+ if err != nil {
+ return transactions, err
+ }
+ if parsedResponse.JSON200 != nil {
+ return *parsedResponse.JSON200, nil
+ }
+ return transactions, nil
+}
diff --git a/client/network/generated.go b/client/network/generated.go
index 47df9a6..b92ba0b 100644
--- a/client/network/generated.go
+++ b/client/network/generated.go
@@ -91,6 +91,15 @@ type RenderGraphParams struct {
End *int `form:"end,omitempty" json:"end,omitempty"`
}
+// ListTransactionsParams defines parameters for ListTransactions.
+type ListTransactionsParams struct {
+ // Start Inclusive start of range (in lamport clock); default=0
+ Start *int `form:"start,omitempty" json:"start,omitempty"`
+
+ // End Exclusive stop of range (in lamport clock); default=∞
+ End *int `form:"end,omitempty" json:"end,omitempty"`
+}
+
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
@@ -175,6 +184,15 @@ type ClientInterface interface {
// ListEvents request
ListEvents(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
+
+ // ListTransactions request
+ ListTransactions(ctx context.Context, params *ListTransactionsParams, reqEditors ...RequestEditorFn) (*http.Response, error)
+
+ // GetTransaction request
+ GetTransaction(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error)
+
+ // GetTransactionPayload request
+ GetTransactionPayload(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error)
}
func (c *Client) GetAddressBook(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
@@ -225,6 +243,42 @@ func (c *Client) ListEvents(ctx context.Context, reqEditors ...RequestEditorFn)
return c.Client.Do(req)
}
+func (c *Client) ListTransactions(ctx context.Context, params *ListTransactionsParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewListTransactionsRequest(c.Server, params)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+ return nil, err
+ }
+ return c.Client.Do(req)
+}
+
+func (c *Client) GetTransaction(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewGetTransactionRequest(c.Server, ref)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+ return nil, err
+ }
+ return c.Client.Do(req)
+}
+
+func (c *Client) GetTransactionPayload(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewGetTransactionPayloadRequest(c.Server, ref)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+ return nil, err
+ }
+ return c.Client.Do(req)
+}
+
// NewGetAddressBookRequest generates requests for GetAddressBook
func NewGetAddressBookRequest(server string) (*http.Request, error) {
var err error
@@ -369,6 +423,137 @@ func NewListEventsRequest(server string) (*http.Request, error) {
return req, nil
}
+// NewListTransactionsRequest generates requests for ListTransactions
+func NewListTransactionsRequest(server string, params *ListTransactionsParams) (*http.Request, error) {
+ var err error
+
+ serverURL, err := url.Parse(server)
+ if err != nil {
+ return nil, err
+ }
+
+ operationPath := fmt.Sprintf("/internal/network/v1/transaction")
+ if operationPath[0] == '/' {
+ operationPath = "." + operationPath
+ }
+
+ queryURL, err := serverURL.Parse(operationPath)
+ if err != nil {
+ return nil, err
+ }
+
+ queryValues := queryURL.Query()
+
+ if params.Start != nil {
+
+ if queryFrag, err := runtime.StyleParamWithLocation("form", true, "start", runtime.ParamLocationQuery, *params.Start); err != nil {
+ return nil, err
+ } else if parsed, err := url.ParseQuery(queryFrag); err != nil {
+ return nil, err
+ } else {
+ for k, v := range parsed {
+ for _, v2 := range v {
+ queryValues.Add(k, v2)
+ }
+ }
+ }
+
+ }
+
+ if params.End != nil {
+
+ if queryFrag, err := runtime.StyleParamWithLocation("form", true, "end", runtime.ParamLocationQuery, *params.End); err != nil {
+ return nil, err
+ } else if parsed, err := url.ParseQuery(queryFrag); err != nil {
+ return nil, err
+ } else {
+ for k, v := range parsed {
+ for _, v2 := range v {
+ queryValues.Add(k, v2)
+ }
+ }
+ }
+
+ }
+
+ queryURL.RawQuery = queryValues.Encode()
+
+ req, err := http.NewRequest("GET", queryURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return req, nil
+}
+
+// NewGetTransactionRequest generates requests for GetTransaction
+func NewGetTransactionRequest(server string, ref string) (*http.Request, error) {
+ var err error
+
+ var pathParam0 string
+
+ pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref)
+ if err != nil {
+ return nil, err
+ }
+
+ serverURL, err := url.Parse(server)
+ if err != nil {
+ return nil, err
+ }
+
+ operationPath := fmt.Sprintf("/internal/network/v1/transaction/%s", pathParam0)
+ if operationPath[0] == '/' {
+ operationPath = "." + operationPath
+ }
+
+ queryURL, err := serverURL.Parse(operationPath)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("GET", queryURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return req, nil
+}
+
+// NewGetTransactionPayloadRequest generates requests for GetTransactionPayload
+func NewGetTransactionPayloadRequest(server string, ref string) (*http.Request, error) {
+ var err error
+
+ var pathParam0 string
+
+ pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref)
+ if err != nil {
+ return nil, err
+ }
+
+ serverURL, err := url.Parse(server)
+ if err != nil {
+ return nil, err
+ }
+
+ operationPath := fmt.Sprintf("/internal/network/v1/transaction/%s/payload", pathParam0)
+ if operationPath[0] == '/' {
+ operationPath = "." + operationPath
+ }
+
+ queryURL, err := serverURL.Parse(operationPath)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("GET", queryURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return req, nil
+}
+
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil {
@@ -423,6 +608,15 @@ type ClientWithResponsesInterface interface {
// ListEvents request
ListEventsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListEventsResponse, error)
+
+ // ListTransactions request
+ ListTransactionsWithResponse(ctx context.Context, params *ListTransactionsParams, reqEditors ...RequestEditorFn) (*ListTransactionsResponse, error)
+
+ // GetTransaction request
+ GetTransactionWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetTransactionResponse, error)
+
+ // GetTransactionPayload request
+ GetTransactionPayloadWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetTransactionPayloadResponse, error)
}
type GetAddressBookResponse struct {
@@ -542,6 +736,100 @@ func (r ListEventsResponse) StatusCode() int {
return 0
}
+type ListTransactionsResponse struct {
+ Body []byte
+ HTTPResponse *http.Response
+ JSON200 *[]string
+ JSONDefault *struct {
+ // Detail A human-readable explanation specific to this occurrence of the problem.
+ Detail string `json:"detail"`
+
+ // Status HTTP statuscode
+ Status float32 `json:"status"`
+
+ // Title A short, human-readable summary of the problem type.
+ Title string `json:"title"`
+ }
+}
+
+// Status returns HTTPResponse.Status
+func (r ListTransactionsResponse) Status() string {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.Status
+ }
+ return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r ListTransactionsResponse) StatusCode() int {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.StatusCode
+ }
+ return 0
+}
+
+type GetTransactionResponse struct {
+ Body []byte
+ HTTPResponse *http.Response
+ JSONDefault *struct {
+ // Detail A human-readable explanation specific to this occurrence of the problem.
+ Detail string `json:"detail"`
+
+ // Status HTTP statuscode
+ Status float32 `json:"status"`
+
+ // Title A short, human-readable summary of the problem type.
+ Title string `json:"title"`
+ }
+}
+
+// Status returns HTTPResponse.Status
+func (r GetTransactionResponse) Status() string {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.Status
+ }
+ return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r GetTransactionResponse) StatusCode() int {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.StatusCode
+ }
+ return 0
+}
+
+type GetTransactionPayloadResponse struct {
+ Body []byte
+ HTTPResponse *http.Response
+ JSONDefault *struct {
+ // Detail A human-readable explanation specific to this occurrence of the problem.
+ Detail string `json:"detail"`
+
+ // Status HTTP statuscode
+ Status float32 `json:"status"`
+
+ // Title A short, human-readable summary of the problem type.
+ Title string `json:"title"`
+ }
+}
+
+// Status returns HTTPResponse.Status
+func (r GetTransactionPayloadResponse) Status() string {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.Status
+ }
+ return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r GetTransactionPayloadResponse) StatusCode() int {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.StatusCode
+ }
+ return 0
+}
+
// GetAddressBookWithResponse request returning *GetAddressBookResponse
func (c *ClientWithResponses) GetAddressBookWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAddressBookResponse, error) {
rsp, err := c.GetAddressBook(ctx, reqEditors...)
@@ -578,6 +866,33 @@ func (c *ClientWithResponses) ListEventsWithResponse(ctx context.Context, reqEdi
return ParseListEventsResponse(rsp)
}
+// ListTransactionsWithResponse request returning *ListTransactionsResponse
+func (c *ClientWithResponses) ListTransactionsWithResponse(ctx context.Context, params *ListTransactionsParams, reqEditors ...RequestEditorFn) (*ListTransactionsResponse, error) {
+ rsp, err := c.ListTransactions(ctx, params, reqEditors...)
+ if err != nil {
+ return nil, err
+ }
+ return ParseListTransactionsResponse(rsp)
+}
+
+// GetTransactionWithResponse request returning *GetTransactionResponse
+func (c *ClientWithResponses) GetTransactionWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetTransactionResponse, error) {
+ rsp, err := c.GetTransaction(ctx, ref, reqEditors...)
+ if err != nil {
+ return nil, err
+ }
+ return ParseGetTransactionResponse(rsp)
+}
+
+// GetTransactionPayloadWithResponse request returning *GetTransactionPayloadResponse
+func (c *ClientWithResponses) GetTransactionPayloadWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetTransactionPayloadResponse, error) {
+ rsp, err := c.GetTransactionPayload(ctx, ref, reqEditors...)
+ if err != nil {
+ return nil, err
+ }
+ return ParseGetTransactionPayloadResponse(rsp)
+}
+
// ParseGetAddressBookResponse parses an HTTP response from a GetAddressBookWithResponse call
func ParseGetAddressBookResponse(rsp *http.Response) (*GetAddressBookResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
@@ -722,3 +1037,115 @@ func ParseListEventsResponse(rsp *http.Response) (*ListEventsResponse, error) {
return response, nil
}
+
+// ParseListTransactionsResponse parses an HTTP response from a ListTransactionsWithResponse call
+func ParseListTransactionsResponse(rsp *http.Response) (*ListTransactionsResponse, error) {
+ bodyBytes, err := io.ReadAll(rsp.Body)
+ defer func() { _ = rsp.Body.Close() }()
+ if err != nil {
+ return nil, err
+ }
+
+ response := &ListTransactionsResponse{
+ Body: bodyBytes,
+ HTTPResponse: rsp,
+ }
+
+ switch {
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+ var dest []string
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSON200 = &dest
+
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
+ var dest struct {
+ // Detail A human-readable explanation specific to this occurrence of the problem.
+ Detail string `json:"detail"`
+
+ // Status HTTP statuscode
+ Status float32 `json:"status"`
+
+ // Title A short, human-readable summary of the problem type.
+ Title string `json:"title"`
+ }
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSONDefault = &dest
+
+ }
+
+ return response, nil
+}
+
+// ParseGetTransactionResponse parses an HTTP response from a GetTransactionWithResponse call
+func ParseGetTransactionResponse(rsp *http.Response) (*GetTransactionResponse, error) {
+ bodyBytes, err := io.ReadAll(rsp.Body)
+ defer func() { _ = rsp.Body.Close() }()
+ if err != nil {
+ return nil, err
+ }
+
+ response := &GetTransactionResponse{
+ Body: bodyBytes,
+ HTTPResponse: rsp,
+ }
+
+ switch {
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
+ var dest struct {
+ // Detail A human-readable explanation specific to this occurrence of the problem.
+ Detail string `json:"detail"`
+
+ // Status HTTP statuscode
+ Status float32 `json:"status"`
+
+ // Title A short, human-readable summary of the problem type.
+ Title string `json:"title"`
+ }
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSONDefault = &dest
+
+ }
+
+ return response, nil
+}
+
+// ParseGetTransactionPayloadResponse parses an HTTP response from a GetTransactionPayloadWithResponse call
+func ParseGetTransactionPayloadResponse(rsp *http.Response) (*GetTransactionPayloadResponse, error) {
+ bodyBytes, err := io.ReadAll(rsp.Body)
+ defer func() { _ = rsp.Body.Close() }()
+ if err != nil {
+ return nil, err
+ }
+
+ response := &GetTransactionPayloadResponse{
+ Body: bodyBytes,
+ HTTPResponse: rsp,
+ }
+
+ switch {
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
+ var dest struct {
+ // Detail A human-readable explanation specific to this occurrence of the problem.
+ Detail string `json:"detail"`
+
+ // Status HTTP statuscode
+ Status float32 `json:"status"`
+
+ // Title A short, human-readable summary of the problem type.
+ Title string `json:"title"`
+ }
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSONDefault = &dest
+
+ }
+
+ return response, nil
+}
diff --git a/codegen/network-config.yaml b/codegen/network-config.yaml
index 0b527ec..2055b9b 100644
--- a/codegen/network-config.yaml
+++ b/codegen/network-config.yaml
@@ -5,4 +5,5 @@ generate:
models: true
output-options:
include-tags:
- - diagnostics
\ No newline at end of file
+ - diagnostics
+ - transactions
\ No newline at end of file
diff --git a/data/README.md b/data/README.md
new file mode 100644
index 0000000..92f7167
--- /dev/null
+++ b/data/README.md
@@ -0,0 +1,11 @@
+Things todo:
+
+Data format
+- map of DID to its controller (cache)
+- map of controller to list of (type, count)
+- map of type to list of (time, count)
+- sliding window (3 times)
+
+Process
+- run at startup
+- listen to nats
\ No newline at end of file
diff --git a/data/store.go b/data/store.go
new file mode 100644
index 0000000..23cc285
--- /dev/null
+++ b/data/store.go
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 Nuts community
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see