Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cmd/control/*
!cmd/control/gitkeep
.DS_Store
.idea
vendor
#vendor
sdk/swagger.yaml
sdk/swagger.yaml-e
sdk/sdks
Expand Down
2 changes: 1 addition & 1 deletion internal/api/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"math/big"

"github.com/formancehq/go-libs/bun/bunpaginate"
"github.com/formancehq/ledger/internal/storage/bunpaginate"

"github.com/formancehq/go-libs/metadata"
"github.com/formancehq/go-libs/migrations"
Expand Down
2 changes: 1 addition & 1 deletion internal/api/backend/backend_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/api/backend/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/formancehq/ledger/internal/storage/sqlutils"

sharedapi "github.com/formancehq/go-libs/api"
sharedapi "github.com/formancehq/ledger/internal/api/sharedapi"

"github.com/pkg/errors"

Expand Down
2 changes: 1 addition & 1 deletion internal/api/read_only.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package api
import (
"net/http"

"github.com/formancehq/go-libs/api"
api "github.com/formancehq/ledger/internal/api/sharedapi"
"github.com/pkg/errors"
)

Expand Down
19 changes: 19 additions & 0 deletions internal/api/sharedapi/handler_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package api

import (
"encoding/json"
"net/http"
)

type ServiceInfo struct {
Version string `json:"version"`
Debug bool `json:"debug"`
}

func InfoHandler(info ServiceInfo) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := json.NewEncoder(w).Encode(info); err != nil {
panic(err)
}
}
}
7 changes: 7 additions & 0 deletions internal/api/sharedapi/idempotency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package api

import "net/http"

func IdempotencyKeyFromRequest(r *http.Request) string {
return r.Header.Get("Idempotency-Key")
}
6 changes: 6 additions & 0 deletions internal/api/sharedapi/link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package api

type Link struct {
Name string `json:"name"`
URI string `json:"uri"`
}
48 changes: 48 additions & 0 deletions internal/api/sharedapi/logging_chi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package api

import (
"fmt"
"net/http"
"time"

"github.com/formancehq/go-libs/logging"
"github.com/go-chi/chi/v5/middleware"
)

type chiLogEntry struct {
r *http.Request
}

func (c *chiLogEntry) Write(status, bytes int, _ http.Header, elapsed time.Duration, extra interface{}) {
fields := map[string]any{
"status": status,
"bytes": bytes,
"elapsed": elapsed,
}
if extra != nil {
fields["extra"] = extra
}
logging.FromContext(c.r.Context()).
WithFields(fields).
Infof("%s %s", c.r.Method, c.r.URL.Path)
}

func (c *chiLogEntry) Panic(v interface{}, stack []byte) {
panic(fmt.Sprintf("%s\n%s", v, stack))
}

var _ middleware.LogEntry = (*chiLogEntry)(nil)

type chiLogFormatter struct{}

func (c chiLogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry {
return &chiLogEntry{
r: r,
}
}

var _ middleware.LogFormatter = (*chiLogFormatter)(nil)

func NewLogFormatter() *chiLogFormatter {
return &chiLogFormatter{}
}
11 changes: 11 additions & 0 deletions internal/api/sharedapi/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package api

import (
"net/http"
"strings"
)

func QueryParamBool(r *http.Request, key string) bool {
v := strings.ToLower(r.URL.Query().Get(key))
return v == "1" || v == "true"
}
64 changes: 64 additions & 0 deletions internal/api/sharedapi/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package api

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/formancehq/ledger/internal/storage/bunpaginate"

"github.com/pkg/errors"
)

type BaseResponse[T any] struct {
Data *T `json:"data,omitempty"`
Cursor *bunpaginate.Cursor[T] `json:"cursor,omitempty"`
}

type ErrorResponse struct {
ErrorCode string `json:"errorCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
Details string `json:"details,omitempty"`
}

func (e ErrorResponse) Error() string {
return fmt.Sprintf("[%s] %s", e.ErrorCode, e.ErrorMessage)
}

func FetchAllPaginated[T any](ctx context.Context, client *http.Client, _url string, queryParams url.Values) ([]T, error) {
ret := make([]T, 0)

var nextToken string
for {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, _url, nil)
if err != nil {
return nil, err
}
if nextToken == "" {
req.URL.RawQuery = queryParams.Encode()
} else {
req.URL.RawQuery = url.Values{
"cursor": []string{nextToken},
}.Encode()
}
rsp, err := client.Do(req)
if err != nil {
return nil, err
}
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d while waiting for %d", rsp.StatusCode, http.StatusOK)
}
apiResponse := BaseResponse[T]{}
if err := json.NewDecoder(rsp.Body).Decode(&apiResponse); err != nil {
return nil, errors.Wrap(err, "decoding cursir")
}
ret = append(ret, apiResponse.Cursor.Data...)
if !apiResponse.Cursor.HasMore {
break
}
nextToken = apiResponse.Cursor.Next
}
return ret, nil
}
30 changes: 30 additions & 0 deletions internal/api/sharedapi/response_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package api

import (
"encoding/json"
"testing"

"github.com/formancehq/go-libs/bun/bunpaginate"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCursor(t *testing.T) {
c := bunpaginate.Cursor[int64]{
Data: []int64{1, 2, 3},
}
by, err := json.Marshal(c)
require.NoError(t, err)
assert.Equal(t, `{"hasMore":false,"data":[1,2,3]}`, string(by))

c = bunpaginate.Cursor[int64]{
Data: []int64{1, 2, 3},
HasMore: true,
}
by, err = json.Marshal(c)
require.NoError(t, err)
assert.Equal(t,
`{"hasMore":true,"data":[1,2,3]}`,
string(by))
}
39 changes: 39 additions & 0 deletions internal/api/sharedapi/response_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package api

import (
"bytes"
"encoding/json"
"io"

"github.com/formancehq/ledger/internal/storage/bunpaginate"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Encode(t require.TestingT, v interface{}) []byte {
data, err := json.Marshal(v)
assert.NoError(t, err)
return data
}

func Buffer(t require.TestingT, v interface{}) *bytes.Buffer {
return bytes.NewBuffer(Encode(t, v))
}

func Decode(t require.TestingT, reader io.Reader, v interface{}) {
err := json.NewDecoder(reader).Decode(v)
require.NoError(t, err)
}

func DecodeSingleResponse[T any](t require.TestingT, reader io.Reader) (T, bool) {
res := BaseResponse[T]{}
Decode(t, reader, &res)
return *res.Data, true
}

func DecodeCursorResponse[T any](t require.TestingT, reader io.Reader) *bunpaginate.Cursor[T] {
res := BaseResponse[T]{}
Decode(t, reader, &res)
return res.Cursor
}
Loading
Loading