diff --git a/api/openapi3.yml b/api/openapi3.yml index 84727e1..8d2dac2 100644 --- a/api/openapi3.yml +++ b/api/openapi3.yml @@ -67,6 +67,21 @@ paths: $ref: "#/components/responses/ListServersResponse" default: $ref: "#/components/responses/ErrorResponse" + /v2/versions/{versionCode}/servers/{serverKey}: + get: + operationId: getServer + tags: + - versions + - servers + description: Get a server + parameters: + - $ref: "#/components/parameters/VersionCodePathParam" + - $ref: "#/components/parameters/ServerKeyPathParam" + responses: + 200: + $ref: "#/components/responses/GetServerResponse" + default: + $ref: "#/components/responses/ErrorResponse" components: schemas: Error: @@ -205,6 +220,12 @@ components: required: true schema: type: string + ServerKeyPathParam: + in: path + name: serverKey + required: true + schema: + type: string responses: ListVersionsResponse: description: "" @@ -247,6 +268,17 @@ components: type: array items: $ref: "#/components/schemas/Server" + GetServerResponse: + description: "" + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Server" ErrorResponse: description: Default error response. content: diff --git a/internal/app/service_server.go b/internal/app/service_server.go index 6e770ae..9ea135e 100644 --- a/internal/app/service_server.go +++ b/internal/app/service_server.go @@ -307,3 +307,36 @@ func (svc *ServerService) UpdatePlayerSnapshotsCreatedAt( return svc.repo.Update(ctx, key, updateParams) } + +func (svc *ServerService) GetNormalByVersionCodeAndServerKey( + ctx context.Context, + versionCode, key string, +) (domain.Server, error) { + params := domain.NewListServersParams() + if err := params.SetVersionCodes([]string{versionCode}); err != nil { + return domain.Server{}, err + } + if err := params.SetKeys([]string{key}); err != nil { + return domain.Server{}, err + } + if err := params.SetSpecial(domain.NullBool{ + Value: false, + Valid: true, + }); err != nil { + return domain.Server{}, err + } + + res, err := svc.repo.List(ctx, params) + if err != nil { + return domain.Server{}, err + } + + servers := res.Servers() + if len(servers) == 0 { + return domain.Server{}, domain.ServerNotFoundError{ + Key: key, + } + } + + return servers[0], nil +} diff --git a/internal/port/handler_http_api.go b/internal/port/handler_http_api.go index d0e3090..9b89c47 100644 --- a/internal/port/handler_http_api.go +++ b/internal/port/handler_http_api.go @@ -62,8 +62,12 @@ func NewAPIHTTPHandler( r.MethodNotAllowed(h.handleMethodNotAllowed) return apimodel.HandlerWithOptions(h, apimodel.ChiServerOptions{ - BaseRouter: r, - Middlewares: []apimodel.MiddlewareFunc{middleware.NoCache}, + BaseRouter: r, + Middlewares: []apimodel.MiddlewareFunc{ + middleware.NoCache, + h.versionMiddleware, + h.serverMiddleware, + }, 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_server.go b/internal/port/handler_http_api_server.go index 1d74e3b..0103b0a 100644 --- a/internal/port/handler_http_api_server.go +++ b/internal/port/handler_http_api_server.go @@ -1,10 +1,13 @@ package port import ( + "context" "net/http" + "slices" "gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel" + "github.com/go-chi/chi/v5" ) func (h *apiHTTPHandler) ListServers( @@ -58,6 +61,53 @@ func (h *apiHTTPHandler) ListServers( renderJSON(w, r, http.StatusOK, apimodel.NewListServersResponse(res)) } +func (h *apiHTTPHandler) GetServer( + w http.ResponseWriter, + r *http.Request, + _ apimodel.VersionCodePathParam, + _ apimodel.ServerKeyPathParam, +) { + server, _ := serverFromContext(r.Context()) + renderJSON(w, r, http.StatusOK, apimodel.NewGetServerResponse(server)) +} + +func (h *apiHTTPHandler) serverMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + routeCtx := chi.RouteContext(ctx) + version, versionOK := versionFromContext(ctx) + serverKeyIdx := slices.Index(routeCtx.URLParams.Keys, "serverKey") + + if !versionOK || serverKeyIdx < 0 { + next.ServeHTTP(w, r) + return + } + + server, err := h.serverSvc.GetNormalByVersionCodeAndServerKey( + ctx, + version.Code(), + routeCtx.URLParams.Values[serverKeyIdx], + ) + if err != nil { + apiErrorRenderer{errors: []error{err}}.render(w, r) + return + } + + next.ServeHTTP(w, r.WithContext(serverToContext(ctx, server))) + }) +} + +type serverCtxKey struct{} + +func serverToContext(ctx context.Context, s domain.Server) context.Context { + return context.WithValue(ctx, serverCtxKey{}, s) +} + +func serverFromContext(ctx context.Context) (domain.Server, bool) { + s, ok := ctx.Value(serverCtxKey{}).(domain.Server) + return s, ok +} + func formatListServersParamsErrorPath(segments []errorPathSegment) []string { if segments[0].model != "ListServersParams" { return nil diff --git a/internal/port/handler_http_api_version.go b/internal/port/handler_http_api_version.go index af540bf..3fee7a2 100644 --- a/internal/port/handler_http_api_version.go +++ b/internal/port/handler_http_api_version.go @@ -1,10 +1,13 @@ package port import ( + "context" "net/http" + "slices" "gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel" + "github.com/go-chi/chi/v5" ) func (h *apiHTTPHandler) ListVersions(w http.ResponseWriter, r *http.Request, params apimodel.ListVersionsParams) { @@ -33,16 +36,43 @@ 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 - } - +func (h *apiHTTPHandler) GetVersion(w http.ResponseWriter, r *http.Request, _ apimodel.VersionCodePathParam) { + version, _ := versionFromContext(r.Context()) renderJSON(w, r, http.StatusOK, apimodel.NewGetVersionResponse(version)) } +func (h *apiHTTPHandler) versionMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + routeCtx := chi.RouteContext(ctx) + idx := slices.Index(routeCtx.URLParams.Keys, "versionCode") + + if idx < 0 { + next.ServeHTTP(w, r) + return + } + + version, err := h.versionSvc.Get(ctx, routeCtx.URLParams.Values[idx]) + if err != nil { + apiErrorRenderer{errors: []error{err}}.render(w, r) + return + } + + next.ServeHTTP(w, r.WithContext(versionToContext(ctx, version))) + }) +} + +type versionCtxKey struct{} + +func versionToContext(ctx context.Context, v domain.Version) context.Context { + return context.WithValue(ctx, versionCtxKey{}, v) +} + +func versionFromContext(ctx context.Context) (domain.Version, bool) { + v, ok := ctx.Value(versionCtxKey{}).(domain.Version) + return v, ok +} + func formatListVersionsParamsErrorPath(segments []errorPathSegment) []string { if segments[0].model != "ListVersionsParams" { return nil diff --git a/internal/port/internal/apimodel/apimodel.go b/internal/port/internal/apimodel/apimodel.go new file mode 100644 index 0000000..5e391ca --- /dev/null +++ b/internal/port/internal/apimodel/apimodel.go @@ -0,0 +1,3 @@ +package apimodel + +//go:generate oapi-codegen --config=config.yml ../../../../api/openapi3.yml diff --git a/internal/port/internal/apimodel/server.go b/internal/port/internal/apimodel/server.go index 8c0d701..b87c6c0 100644 --- a/internal/port/internal/apimodel/server.go +++ b/internal/port/internal/apimodel/server.go @@ -54,3 +54,9 @@ func NewListServersResponse(res domain.ListServersResult) ListServersResponse { return resp } + +func NewGetServerResponse(v domain.Server) GetServerResponse { + return GetServerResponse{ + Data: NewServer(v), + } +} diff --git a/internal/port/internal/apimodel/version.go b/internal/port/internal/apimodel/version.go index 7460bb1..da4361a 100644 --- a/internal/port/internal/apimodel/version.go +++ b/internal/port/internal/apimodel/version.go @@ -2,8 +2,6 @@ package apimodel import "gitea.dwysokinski.me/twhelp/corev3/internal/domain" -//go:generate oapi-codegen --config=config.yml ../../../../api/openapi3.yml - func NewVersion(v domain.Version) Version { return Version{ Code: v.Code(),