feat: api - add a new endpoint - GET /api/v2/versions/{versionCode}

This commit is contained in:
Dawid Wysokiński 2024-02-01 07:39:38 +01:00
parent 28877f1b9b
commit a66bbb0b2f
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
7 changed files with 149 additions and 19 deletions

View File

@ -36,6 +36,19 @@ paths:
$ref: "#/components/responses/ListVersionsResponse"
default:
$ref: "#/components/responses/ErrorResponse"
/v2/versions/{versionCode}:
get:
operationId: getVersion
tags:
- versions
description: Get a version
parameters:
- $ref: "#/components/parameters/VersionCodePathParam"
responses:
200:
$ref: "#/components/responses/GetVersionResponse"
default:
$ref: "#/components/responses/ErrorResponse"
components:
schemas:
Error:
@ -81,29 +94,23 @@ components:
timezone:
type: string
example: Europe/Warsaw
PaginationResponse:
Cursor:
type: object
x-go-type-skip-optional-pointer: true
properties:
self:
description: Cursor pointing to the current page.
type: string
x-go-type-skip-optional-pointer: true
first:
description: Cursor pointing to the first page.
type: string
x-go-type-skip-optional-pointer: true
prev:
description: Cursor pointing to the previous page.
type: string
x-go-type-skip-optional-pointer: true
next:
description: Cursor pointing to the next page.
type: string
x-go-type-skip-optional-pointer: true
last:
description: Cursor pointing to the last page.
type: string
x-go-type-skip-optional-pointer: true
PaginationResponse:
type: object
properties:
cursor:
$ref: "#/components/schemas/Cursor"
parameters:
CursorQueryParam:
in: query
@ -117,6 +124,12 @@ components:
schema:
type: integer
required: false
VersionCodePathParam:
in: path
name: versionCode
required: true
schema:
type: string
responses:
ListVersionsResponse:
description: ""
@ -127,12 +140,23 @@ components:
- $ref: "#/components/schemas/PaginationResponse"
- type: object
required:
- items
- data
properties:
items:
data:
type: array
items:
$ref: "#/components/schemas/Version"
GetVersionResponse:
description: ""
content:
application/json:
schema:
type: object
required:
- data
properties:
data:
$ref: "#/components/schemas/Version"
ErrorResponse:
description: Default error response.
content:

View File

@ -45,6 +45,10 @@ type listVersionsParamsApplier struct {
}
func (a listVersionsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if codes := a.params.Codes(); len(codes) > 0 {
q = q.Where("version.code IN (?)", bun.In(codes))
}
for _, s := range a.params.Sort() {
codeCursor := a.params.Cursor().Code()

View File

@ -139,6 +139,36 @@ func testVersionRepository(t *testing.T, newRepos func(t *testing.T) repositorie
require.NoError(t, err)
},
},
{
name: "OK: codes limit=1",
params: func(t *testing.T) domain.ListVersionsParams {
t.Helper()
params := domain.NewListVersionsParams()
require.NoError(t, params.SetLimit(1))
res, err := repos.version.List(ctx, params)
require.NoError(t, err)
require.NotEmpty(t, res)
require.NoError(t, params.SetCodes([]string{res.Versions()[0].Code()}))
return params
},
assertResult: func(t *testing.T, params domain.ListVersionsParams, res domain.ListVersionsResult) {
t.Helper()
versions := res.Versions()
codes := params.Codes()
assert.Len(t, versions, len(codes))
for _, v := range versions {
assert.Contains(t, codes, v.Code())
}
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
},
{
name: "OK: limit=5",
params: func(t *testing.T) domain.ListVersionsParams {

View File

@ -24,3 +24,24 @@ func (svc *VersionService) List(
) (domain.ListVersionsResult, error) {
return svc.repo.List(ctx, params)
}
func (svc *VersionService) Get(ctx context.Context, code string) (domain.Version, error) {
params := domain.NewListVersionsParams()
if err := params.SetCodes([]string{code}); err != nil {
return domain.Version{}, err
}
res, err := svc.repo.List(ctx, params)
if err != nil {
return domain.Version{}, err
}
versions := res.Versions()
if len(versions) == 0 {
return domain.Version{}, domain.VersionNotFoundError{
VersionCode: code,
}
}
return versions[0], nil
}

View File

@ -2,6 +2,7 @@ package domain
import (
"encoding/base64"
"fmt"
"net/url"
)
@ -172,6 +173,7 @@ func (vc VersionCursor) Encode() string {
}
type ListVersionsParams struct {
codes []string
sort []VersionSort
cursor VersionCursor
limit int
@ -189,6 +191,15 @@ func NewListVersionsParams() ListVersionsParams {
}
}
func (params *ListVersionsParams) Codes() []string {
return params.codes
}
func (params *ListVersionsParams) SetCodes(codes []string) error {
params.codes = codes
return nil
}
const (
versionSortMinLength = 1
versionSortMaxLength = 1
@ -308,3 +319,25 @@ func (res ListVersionsResult) Self() VersionCursor {
func (res ListVersionsResult) Next() VersionCursor {
return res.next
}
type VersionNotFoundError struct {
VersionCode string
}
func (e VersionNotFoundError) Error() string {
return fmt.Sprintf("version with code %s not found", e.VersionCode)
}
func (e VersionNotFoundError) Code() ErrorCode {
return ErrorCodeNotFound
}
func (e VersionNotFoundError) Slug() string {
return "version-not-found"
}
func (e VersionNotFoundError) Params() map[string]any {
return map[string]any{
"Code": e.VersionCode,
}
}

View File

@ -33,6 +33,16 @@ func (h *apiHTTPHandler) ListVersions(w http.ResponseWriter, r *http.Request, pa
renderJSON(w, r, http.StatusOK, apimodel.NewListVersionsResponse(res))
}
func (h *apiHTTPHandler) GetVersion(w http.ResponseWriter, r *http.Request, versionCode apimodel.VersionCodePathParam) {
version, err := h.versionSvc.Get(r.Context(), versionCode)
if err != nil {
apiErrorRenderer{errors: []error{err}}.render(w, r)
return
}
renderJSON(w, r, http.StatusOK, apimodel.NewGetVersionResponse(version))
}
func formatListVersionsParamsErrorPath(elems []errorPathElement) []string {
if elems[0].model != "ListVersionsParams" {
return nil

View File

@ -17,14 +17,22 @@ func NewListVersionsResponse(res domain.ListVersionsResult) ListVersionsResponse
versions := res.Versions()
resp := ListVersionsResponse{
Items: make([]Version, 0, len(versions)),
Self: res.Self().Encode(),
Next: res.Next().Encode(),
Data: make([]Version, 0, len(versions)),
Cursor: Cursor{
Next: res.Next().Encode(),
Self: res.Self().Encode(),
},
}
for _, v := range versions {
resp.Items = append(resp.Items, NewVersion(v))
resp.Data = append(resp.Data, NewVersion(v))
}
return resp
}
func NewGetVersionResponse(v domain.Version) GetVersionResponse {
return GetVersionResponse{
Data: NewVersion(v),
}
}