From 30be8bebc03577d7e516b1ff5b844825c5dd426e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Tue, 20 Feb 2024 08:14:35 +0100 Subject: [PATCH] feat: new API endpoint - GET /api/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId} --- api/openapi3.yml | 37 +++++++++++- internal/app/service_tribe.go | 29 +++++++++ internal/domain/tribe.go | 26 ++++++++ internal/port/handler_http_api.go | 1 + internal/port/handler_http_api_tribe.go | 75 ++++++++++++++++++++++++ internal/port/internal/apimodel/tribe.go | 6 ++ 6 files changed, 172 insertions(+), 2 deletions(-) diff --git a/api/openapi3.yml b/api/openapi3.yml index cbcd370..6e43828 100644 --- a/api/openapi3.yml +++ b/api/openapi3.yml @@ -149,7 +149,23 @@ paths: $ref: "#/components/responses/ListTribesResponse" default: $ref: "#/components/responses/ErrorResponse" - + /v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}: + get: + operationId: getTribe + tags: + - versions + - servers + - tribes + description: Get a tribe + parameters: + - $ref: "#/components/parameters/VersionCodePathParam" + - $ref: "#/components/parameters/ServerKeyPathParam" + - $ref: "#/components/parameters/TribeIdPathParam" + responses: + 200: + $ref: "#/components/responses/GetTribeResponse" + default: + $ref: "#/components/responses/ErrorResponse" components: schemas: Error: @@ -993,6 +1009,13 @@ components: required: true schema: type: string + TribeIdPathParam: + in: path + name: tribeId + required: true + schema: + type: integer + minimum: 0 responses: ListVersionsResponse: description: "" @@ -1094,7 +1117,17 @@ components: type: array items: $ref: "#/components/schemas/Tribe" - + GetTribeResponse: + description: "" + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Tribe" ErrorResponse: description: Default error response. content: diff --git a/internal/app/service_tribe.go b/internal/app/service_tribe.go index 1a9b7bc..d9d280a 100644 --- a/internal/app/service_tribe.go +++ b/internal/app/service_tribe.go @@ -157,3 +157,32 @@ func (svc *TribeService) UpdateDominance(ctx context.Context, payload domain.Vil func (svc *TribeService) List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error) { return svc.repo.List(ctx, params) } + +func (svc *TribeService) Get( + ctx context.Context, + id int, + serverKey string, +) (domain.Tribe, error) { + params := domain.NewListTribesParams() + if err := params.SetIDs([]int{id}); err != nil { + return domain.Tribe{}, err + } + if err := params.SetServerKeys([]string{serverKey}); err != nil { + return domain.Tribe{}, err + } + + res, err := svc.repo.List(ctx, params) + if err != nil { + return domain.Tribe{}, err + } + + tribes := res.Tribes() + if len(tribes) == 0 { + return domain.Tribe{}, domain.TribeNotFoundError{ + ID: id, + ServerKey: serverKey, + } + } + + return tribes[0], nil +} diff --git a/internal/domain/tribe.go b/internal/domain/tribe.go index 215d37c..708d95e 100644 --- a/internal/domain/tribe.go +++ b/internal/domain/tribe.go @@ -862,3 +862,29 @@ func (res ListTribesResult) Self() TribeCursor { func (res ListTribesResult) Next() TribeCursor { return res.next } + +type TribeNotFoundError struct { + ID int + ServerKey string +} + +var _ ErrorWithParams = TribeNotFoundError{} + +func (e TribeNotFoundError) Error() string { + return fmt.Sprintf("tribe with id %d and server key %s not found", e.ID, e.ServerKey) +} + +func (e TribeNotFoundError) Type() ErrorType { + return ErrorTypeNotFound +} + +func (e TribeNotFoundError) Code() string { + return "tribe-not-found" +} + +func (e TribeNotFoundError) Params() map[string]any { + return map[string]any{ + "ID": e.ID, + "ServerKey": e.ServerKey, + } +} diff --git a/internal/port/handler_http_api.go b/internal/port/handler_http_api.go index 8f35de3..8484774 100644 --- a/internal/port/handler_http_api.go +++ b/internal/port/handler_http_api.go @@ -70,6 +70,7 @@ func NewAPIHTTPHandler( middleware.NoCache, h.versionMiddleware, h.serverMiddleware, + h.tribeMiddleware, }, ErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { apiErrorRenderer{errors: []error{err}}.render(w, r) diff --git a/internal/port/handler_http_api_tribe.go b/internal/port/handler_http_api_tribe.go index 9df328d..79003ab 100644 --- a/internal/port/handler_http_api_tribe.go +++ b/internal/port/handler_http_api_tribe.go @@ -1,11 +1,15 @@ package port import ( + "context" + "fmt" "net/http" + "slices" "strconv" "gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel" + "github.com/go-chi/chi/v5" ) //nolint:gocyclo @@ -75,6 +79,60 @@ func (h *apiHTTPHandler) ListTribes( renderJSON(w, r, http.StatusOK, apimodel.NewListTribesResponse(res)) } +func (h *apiHTTPHandler) GetTribe( + w http.ResponseWriter, + r *http.Request, + _ apimodel.VersionCodePathParam, + _ apimodel.ServerKeyPathParam, + _ apimodel.TribeIdPathParam, +) { + tribe, _ := tribeFromContext(r.Context()) + renderJSON(w, r, http.StatusOK, apimodel.NewGetTribeResponse(tribe)) +} + +func (h *apiHTTPHandler) tribeMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + routeCtx := chi.RouteContext(ctx) + server, serverOK := serverFromContext(ctx) + tribeIDIdx := slices.Index(routeCtx.URLParams.Keys, "tribeId") + + if !serverOK || tribeIDIdx < 0 { + next.ServeHTTP(w, r) + return + } + + tribeID, err := strconv.Atoi(routeCtx.URLParams.Values[tribeIDIdx]) + if err != nil { + return + } + + tribe, err := h.tribeSvc.Get( + ctx, + tribeID, + server.Key(), + ) + if err != nil { + fmt.Println(err) + apiErrorRenderer{errors: []error{err}, formatErrorPath: formatGetTribeErrorPath}.render(w, r) + return + } + + next.ServeHTTP(w, r.WithContext(tribeToContext(ctx, tribe))) + }) +} + +type tribeCtxKey struct{} + +func tribeToContext(ctx context.Context, t domain.Tribe) context.Context { + return context.WithValue(ctx, tribeCtxKey{}, t) +} + +func tribeFromContext(ctx context.Context) (domain.Tribe, bool) { + t, ok := ctx.Value(tribeCtxKey{}).(domain.Tribe) + return t, ok +} + func formatListTribesParamsErrorPath(segments []errorPathSegment) []string { if segments[0].model != "ListTribesParams" { return nil @@ -97,3 +155,20 @@ func formatListTribesParamsErrorPath(segments []errorPathSegment) []string { return nil } } + +func formatGetTribeErrorPath(segments []errorPathSegment) []string { + if segments[0].model != "ListTribesParams" { + return nil + } + + switch segments[0].field { + case "ids": + path := []string{"$path", "ids"} + if segments[0].index >= 0 { + path = append(path, strconv.Itoa(segments[0].index)) + } + return path + default: + return nil + } +} diff --git a/internal/port/internal/apimodel/tribe.go b/internal/port/internal/apimodel/tribe.go index 19cf406..dfe8f93 100644 --- a/internal/port/internal/apimodel/tribe.go +++ b/internal/port/internal/apimodel/tribe.go @@ -61,3 +61,9 @@ func NewListTribesResponse(res domain.ListTribesResult) ListTribesResponse { return resp } + +func NewGetTribeResponse(t domain.Tribe) GetTribeResponse { + return GetTribeResponse{ + Data: NewTribe(t), + } +}