From d5d794703b334ddff66297226e00c8410e84d4ed Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Wed, 23 Jul 2025 01:54:04 -0700 Subject: [PATCH 1/6] submissions: missing inner service AuditEvent functions currently unused --- pkg/service/audit_events.go | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pkg/service/audit_events.go b/pkg/service/audit_events.go index 735ae9a..eaf761e 100644 --- a/pkg/service/audit_events.go +++ b/pkg/service/audit_events.go @@ -194,3 +194,45 @@ func (svc *Service) CreateAuditEventCheckList(ctx context.Context, userId uint64 return nil } + +func (svc *Service) CreateAuditEventChangeDisplayName(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeName) error { + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: resource.Type, + ResourceID: resource.ID, + EventType: model.AuditEventTypeChangeDisplayName, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} + +func (svc *Service) CreateAuditEventChangeCreator(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeName) error { + EventData, err := json.Marshal(event_data) + if err != nil { + return err + } + + _, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{ + ID: 0, + User: userId, + ResourceType: resource.Type, + ResourceID: resource.ID, + EventType: model.AuditEventTypeChangeCreator, + EventData: EventData, + }) + if err != nil { + return err + } + + return nil +} From a8ad9f7de0de99c731a2c9960f91eb5d34a99ab9 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 24 Jun 2025 05:30:20 -0700 Subject: [PATCH 2/6] ai thinking Rust while doing golang lol --- pkg/roblox/asset_location.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/roblox/asset_location.go b/pkg/roblox/asset_location.go index 0c6a1e5..2b4a140 100644 --- a/pkg/roblox/asset_location.go +++ b/pkg/roblox/asset_location.go @@ -55,7 +55,7 @@ func (c *Client) GetAssetLocation(config GetAssetLatestRequest) (*AssetLocationI resp, err := c.HttpClient.Do(req) if err != nil { - return nil, GetError("ReqwestError: " + err.Error()) + return nil, GetError("RequestError: " + err.Error()) } defer resp.Body.Close() From cc93776c25c222236b029edf36eae02f4c2ea7f3 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 24 Jun 2025 05:17:14 -0700 Subject: [PATCH 3/6] openapi: change to proxy download --- openapi.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 85ca8e7..89cdcd1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -158,10 +158,10 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - /maps/{MapID}/location: + /maps/{MapID}/download: get: - summary: Get location of asset - operationId: getMapAssetLocation + summary: Download the map asset + operationId: downloadMapAsset tags: - Maps parameters: @@ -176,10 +176,10 @@ paths: "200": description: Successful response content: - text/plain: + application/octet-stream: schema: type: string - maxLength: 1024 + format: binary default: description: General Error content: From 03ec0b0183f02a859eb770f01371541db8890e78 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 24 Jun 2025 05:17:20 -0700 Subject: [PATCH 4/6] openapi: generate --- pkg/api/oas_client_gen.go | 260 +++++++++--------- pkg/api/oas_handlers_gen.go | 390 +++++++++++++-------------- pkg/api/oas_operations_gen.go | 2 +- pkg/api/oas_parameters_gen.go | 18 +- pkg/api/oas_response_decoders_gen.go | 74 ++--- pkg/api/oas_response_encoders_gen.go | 32 +-- pkg/api/oas_router_gen.go | 18 +- pkg/api/oas_schemas_gen.go | 28 +- pkg/api/oas_security_gen.go | 2 +- pkg/api/oas_server_gen.go | 12 +- pkg/api/oas_unimplemented_gen.go | 18 +- 11 files changed, 427 insertions(+), 427 deletions(-) diff --git a/pkg/api/oas_client_gen.go b/pkg/api/oas_client_gen.go index 567b71f..bc9a36b 100644 --- a/pkg/api/oas_client_gen.go +++ b/pkg/api/oas_client_gen.go @@ -217,18 +217,18 @@ type Invoker interface { // // DELETE /script-policy/{ScriptPolicyID} DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error + // DownloadMapAsset invokes downloadMapAsset operation. + // + // Download the map asset. + // + // GET /maps/{MapID}/download + DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (DownloadMapAssetOK, error) // GetMap invokes getMap operation. // // Retrieve map with ID. // // GET /maps/{MapID} GetMap(ctx context.Context, params GetMapParams) (*Map, error) - // GetMapAssetLocation invokes getMapAssetLocation operation. - // - // Get location of asset. - // - // GET /maps/{MapID}/location - GetMapAssetLocation(ctx context.Context, params GetMapAssetLocationParams) (GetMapAssetLocationOK, error) // GetMapfix invokes getMapfix operation. // // Retrieve map with ID. @@ -4182,6 +4182,130 @@ func (c *Client) sendDeleteScriptPolicy(ctx context.Context, params DeleteScript return result, nil } +// DownloadMapAsset invokes downloadMapAsset operation. +// +// Download the map asset. +// +// GET /maps/{MapID}/download +func (c *Client) DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (DownloadMapAssetOK, error) { + res, err := c.sendDownloadMapAsset(ctx, params) + return res, err +} + +func (c *Client) sendDownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (res DownloadMapAssetOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("downloadMapAsset"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/maps/{MapID}/download"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, DownloadMapAssetOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [3]string + pathParts[0] = "/maps/" + { + // Encode "MapID" parameter. + e := uri.NewPathEncoder(uri.PathEncoderConfig{ + Param: "MapID", + Style: uri.PathStyleSimple, + Explode: false, + }) + if err := func() error { + return e.EncodeValue(conv.Int64ToString(params.MapID)) + }(); err != nil { + return res, errors.Wrap(err, "encode path") + } + encoded, err := e.Result() + if err != nil { + return res, errors.Wrap(err, "encode path") + } + pathParts[1] = encoded + } + pathParts[2] = "/download" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + { + type bitset = [1]uint8 + var satisfied bitset + { + stage = "Security:CookieAuth" + switch err := c.securityCookieAuth(ctx, DownloadMapAssetOperation, r); { + case err == nil: // if NO error + satisfied[0] |= 1 << 0 + case errors.Is(err, ogenerrors.ErrSkipClientSecurity): + // Skip this security. + default: + return res, errors.Wrap(err, "security \"CookieAuth\"") + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied + } + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeDownloadMapAssetResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + // GetMap invokes getMap operation. // // Retrieve map with ID. @@ -4272,130 +4396,6 @@ func (c *Client) sendGetMap(ctx context.Context, params GetMapParams) (res *Map, return result, nil } -// GetMapAssetLocation invokes getMapAssetLocation operation. -// -// Get location of asset. -// -// GET /maps/{MapID}/location -func (c *Client) GetMapAssetLocation(ctx context.Context, params GetMapAssetLocationParams) (GetMapAssetLocationOK, error) { - res, err := c.sendGetMapAssetLocation(ctx, params) - return res, err -} - -func (c *Client) sendGetMapAssetLocation(ctx context.Context, params GetMapAssetLocationParams) (res GetMapAssetLocationOK, err error) { - otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("getMapAssetLocation"), - semconv.HTTPRequestMethodKey.String("GET"), - semconv.HTTPRouteKey.String("/maps/{MapID}/location"), - } - - // Run stopwatch. - startTime := time.Now() - defer func() { - // Use floating point division here for higher precision (instead of Millisecond method). - elapsedDuration := time.Since(startTime) - c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) - }() - - // Increment request counter. - c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) - - // Start a span for this request. - ctx, span := c.cfg.Tracer.Start(ctx, GetMapAssetLocationOperation, - trace.WithAttributes(otelAttrs...), - clientSpanKind, - ) - // Track stage for error reporting. - var stage string - defer func() { - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, stage) - c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) - } - span.End() - }() - - stage = "BuildURL" - u := uri.Clone(c.requestURL(ctx)) - var pathParts [3]string - pathParts[0] = "/maps/" - { - // Encode "MapID" parameter. - e := uri.NewPathEncoder(uri.PathEncoderConfig{ - Param: "MapID", - Style: uri.PathStyleSimple, - Explode: false, - }) - if err := func() error { - return e.EncodeValue(conv.Int64ToString(params.MapID)) - }(); err != nil { - return res, errors.Wrap(err, "encode path") - } - encoded, err := e.Result() - if err != nil { - return res, errors.Wrap(err, "encode path") - } - pathParts[1] = encoded - } - pathParts[2] = "/location" - uri.AddPathParts(u, pathParts[:]...) - - stage = "EncodeRequest" - r, err := ht.NewRequest(ctx, "GET", u) - if err != nil { - return res, errors.Wrap(err, "create request") - } - - { - type bitset = [1]uint8 - var satisfied bitset - { - stage = "Security:CookieAuth" - switch err := c.securityCookieAuth(ctx, GetMapAssetLocationOperation, r); { - case err == nil: // if NO error - satisfied[0] |= 1 << 0 - case errors.Is(err, ogenerrors.ErrSkipClientSecurity): - // Skip this security. - default: - return res, errors.Wrap(err, "security \"CookieAuth\"") - } - } - - if ok := func() bool { - nextRequirement: - for _, requirement := range []bitset{ - {0b00000001}, - } { - for i, mask := range requirement { - if satisfied[i]&mask != mask { - continue nextRequirement - } - } - return true - } - return false - }(); !ok { - return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied - } - } - - stage = "SendRequest" - resp, err := c.cfg.Client.Do(r) - if err != nil { - return res, errors.Wrap(err, "do request") - } - defer resp.Body.Close() - - stage = "DecodeResponse" - result, err := decodeGetMapAssetLocationResponse(resp) - if err != nil { - return res, errors.Wrap(err, "decode response") - } - - return result, nil -} - // GetMapfix invokes getMapfix operation. // // Retrieve map with ID. diff --git a/pkg/api/oas_handlers_gen.go b/pkg/api/oas_handlers_gen.go index 7a36a93..197e414 100644 --- a/pkg/api/oas_handlers_gen.go +++ b/pkg/api/oas_handlers_gen.go @@ -6107,6 +6107,201 @@ func (s *Server) handleDeleteScriptPolicyRequest(args [1]string, argsEscaped boo } } +// handleDownloadMapAssetRequest handles downloadMapAsset operation. +// +// Download the map asset. +// +// GET /maps/{MapID}/download +func (s *Server) handleDownloadMapAssetRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("downloadMapAsset"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/maps/{MapID}/download"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), DownloadMapAssetOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: DownloadMapAssetOperation, + ID: "downloadMapAsset", + } + ) + { + type bitset = [1]uint8 + var satisfied bitset + { + sctx, ok, err := s.securityCookieAuth(ctx, DownloadMapAssetOperation, r) + if err != nil { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Security: "CookieAuth", + Err: err, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security:CookieAuth", err) + } + return + } + if ok { + satisfied[0] |= 1 << 0 + ctx = sctx + } + } + + if ok := func() bool { + nextRequirement: + for _, requirement := range []bitset{ + {0b00000001}, + } { + for i, mask := range requirement { + if satisfied[i]&mask != mask { + continue nextRequirement + } + } + return true + } + return false + }(); !ok { + err = &ogenerrors.SecurityError{ + OperationContext: opErrContext, + Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, + } + if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { + defer recordError("Security", err) + } + return + } + } + params, err := decodeDownloadMapAssetParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response DownloadMapAssetOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: DownloadMapAssetOperation, + OperationSummary: "Download the map asset", + OperationID: "downloadMapAsset", + Body: nil, + Params: middleware.Parameters{ + { + Name: "MapID", + In: "path", + }: params.MapID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = DownloadMapAssetParams + Response = DownloadMapAssetOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackDownloadMapAssetParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.DownloadMapAsset(ctx, params) + return response, err + }, + ) + } else { + response, err = s.h.DownloadMapAsset(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeDownloadMapAssetResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleGetMapRequest handles getMap operation. // // Retrieve map with ID. @@ -6256,201 +6451,6 @@ func (s *Server) handleGetMapRequest(args [1]string, argsEscaped bool, w http.Re } } -// handleGetMapAssetLocationRequest handles getMapAssetLocation operation. -// -// Get location of asset. -// -// GET /maps/{MapID}/location -func (s *Server) handleGetMapAssetLocationRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { - statusWriter := &codeRecorder{ResponseWriter: w} - w = statusWriter - otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("getMapAssetLocation"), - semconv.HTTPRequestMethodKey.String("GET"), - semconv.HTTPRouteKey.String("/maps/{MapID}/location"), - } - - // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), GetMapAssetLocationOperation, - trace.WithAttributes(otelAttrs...), - serverSpanKind, - ) - defer span.End() - - // Add Labeler to context. - labeler := &Labeler{attrs: otelAttrs} - ctx = contextWithLabeler(ctx, labeler) - - // Run stopwatch. - startTime := time.Now() - defer func() { - elapsedDuration := time.Since(startTime) - - attrSet := labeler.AttributeSet() - attrs := attrSet.ToSlice() - code := statusWriter.status - if code != 0 { - codeAttr := semconv.HTTPResponseStatusCode(code) - attrs = append(attrs, codeAttr) - span.SetAttributes(codeAttr) - } - attrOpt := metric.WithAttributes(attrs...) - - // Increment request counter. - s.requests.Add(ctx, 1, attrOpt) - - // Use floating point division here for higher precision (instead of Millisecond method). - s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) - }() - - var ( - recordError = func(stage string, err error) { - span.RecordError(err) - - // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status - // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, - // unless there was another error (e.g., network error receiving the response body; or 3xx codes with - // max redirects exceeded), in which case status MUST be set to Error. - code := statusWriter.status - if code >= 100 && code < 500 { - span.SetStatus(codes.Error, stage) - } - - attrSet := labeler.AttributeSet() - attrs := attrSet.ToSlice() - if code != 0 { - attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) - } - - s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) - } - err error - opErrContext = ogenerrors.OperationContext{ - Name: GetMapAssetLocationOperation, - ID: "getMapAssetLocation", - } - ) - { - type bitset = [1]uint8 - var satisfied bitset - { - sctx, ok, err := s.securityCookieAuth(ctx, GetMapAssetLocationOperation, r) - if err != nil { - err = &ogenerrors.SecurityError{ - OperationContext: opErrContext, - Security: "CookieAuth", - Err: err, - } - if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { - defer recordError("Security:CookieAuth", err) - } - return - } - if ok { - satisfied[0] |= 1 << 0 - ctx = sctx - } - } - - if ok := func() bool { - nextRequirement: - for _, requirement := range []bitset{ - {0b00000001}, - } { - for i, mask := range requirement { - if satisfied[i]&mask != mask { - continue nextRequirement - } - } - return true - } - return false - }(); !ok { - err = &ogenerrors.SecurityError{ - OperationContext: opErrContext, - Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied, - } - if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil { - defer recordError("Security", err) - } - return - } - } - params, err := decodeGetMapAssetLocationParams(args, argsEscaped, r) - if err != nil { - err = &ogenerrors.DecodeParamsError{ - OperationContext: opErrContext, - Err: err, - } - defer recordError("DecodeParams", err) - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - - var response GetMapAssetLocationOK - if m := s.cfg.Middleware; m != nil { - mreq := middleware.Request{ - Context: ctx, - OperationName: GetMapAssetLocationOperation, - OperationSummary: "Get location of asset", - OperationID: "getMapAssetLocation", - Body: nil, - Params: middleware.Parameters{ - { - Name: "MapID", - In: "path", - }: params.MapID, - }, - Raw: r, - } - - type ( - Request = struct{} - Params = GetMapAssetLocationParams - Response = GetMapAssetLocationOK - ) - response, err = middleware.HookMiddleware[ - Request, - Params, - Response, - ]( - m, - mreq, - unpackGetMapAssetLocationParams, - func(ctx context.Context, request Request, params Params) (response Response, err error) { - response, err = s.h.GetMapAssetLocation(ctx, params) - return response, err - }, - ) - } else { - response, err = s.h.GetMapAssetLocation(ctx, params) - } - if err != nil { - if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { - if err := encodeErrorResponse(errRes, w, span); err != nil { - defer recordError("Internal", err) - } - return - } - if errors.Is(err, ht.ErrNotImplemented) { - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { - defer recordError("Internal", err) - } - return - } - - if err := encodeGetMapAssetLocationResponse(response, w, span); err != nil { - defer recordError("EncodeResponse", err) - if !errors.Is(err, ht.ErrInternalServerErrorResponse) { - s.cfg.ErrorHandler(ctx, w, r, err) - } - return - } -} - // handleGetMapfixRequest handles getMapfix operation. // // Retrieve map with ID. diff --git a/pkg/api/oas_operations_gen.go b/pkg/api/oas_operations_gen.go index e448a29..b5a33a8 100644 --- a/pkg/api/oas_operations_gen.go +++ b/pkg/api/oas_operations_gen.go @@ -37,8 +37,8 @@ const ( CreateSubmissionAuditCommentOperation OperationName = "CreateSubmissionAuditComment" DeleteScriptOperation OperationName = "DeleteScript" DeleteScriptPolicyOperation OperationName = "DeleteScriptPolicy" + DownloadMapAssetOperation OperationName = "DownloadMapAsset" GetMapOperation OperationName = "GetMap" - GetMapAssetLocationOperation OperationName = "GetMapAssetLocation" GetMapfixOperation OperationName = "GetMapfix" GetOperationOperation OperationName = "GetOperation" GetScriptOperation OperationName = "GetScript" diff --git a/pkg/api/oas_parameters_gen.go b/pkg/api/oas_parameters_gen.go index c5c4fed..ac8c0f6 100644 --- a/pkg/api/oas_parameters_gen.go +++ b/pkg/api/oas_parameters_gen.go @@ -2173,13 +2173,12 @@ func decodeDeleteScriptPolicyParams(args [1]string, argsEscaped bool, r *http.Re return params, nil } -// GetMapParams is parameters of getMap operation. -type GetMapParams struct { - // The unique identifier for a map. +// DownloadMapAssetParams is parameters of downloadMapAsset operation. +type DownloadMapAssetParams struct { MapID int64 } -func unpackGetMapParams(packed middleware.Parameters) (params GetMapParams) { +func unpackDownloadMapAssetParams(packed middleware.Parameters) (params DownloadMapAssetParams) { { key := middleware.ParameterKey{ Name: "MapID", @@ -2190,7 +2189,7 @@ func unpackGetMapParams(packed middleware.Parameters) (params GetMapParams) { return params } -func decodeGetMapParams(args [1]string, argsEscaped bool, r *http.Request) (params GetMapParams, _ error) { +func decodeDownloadMapAssetParams(args [1]string, argsEscaped bool, r *http.Request) (params DownloadMapAssetParams, _ error) { // Decode path: MapID. if err := func() error { param := args[0] @@ -2256,12 +2255,13 @@ func decodeGetMapParams(args [1]string, argsEscaped bool, r *http.Request) (para return params, nil } -// GetMapAssetLocationParams is parameters of getMapAssetLocation operation. -type GetMapAssetLocationParams struct { +// GetMapParams is parameters of getMap operation. +type GetMapParams struct { + // The unique identifier for a map. MapID int64 } -func unpackGetMapAssetLocationParams(packed middleware.Parameters) (params GetMapAssetLocationParams) { +func unpackGetMapParams(packed middleware.Parameters) (params GetMapParams) { { key := middleware.ParameterKey{ Name: "MapID", @@ -2272,7 +2272,7 @@ func unpackGetMapAssetLocationParams(packed middleware.Parameters) (params GetMa return params } -func decodeGetMapAssetLocationParams(args [1]string, argsEscaped bool, r *http.Request) (params GetMapAssetLocationParams, _ error) { +func decodeGetMapParams(args [1]string, argsEscaped bool, r *http.Request) (params GetMapParams, _ error) { // Decode path: MapID. if err := func() error { param := args[0] diff --git a/pkg/api/oas_response_decoders_gen.go b/pkg/api/oas_response_decoders_gen.go index 1f8d947..958d0d9 100644 --- a/pkg/api/oas_response_decoders_gen.go +++ b/pkg/api/oas_response_decoders_gen.go @@ -2081,7 +2081,7 @@ func decodeDeleteScriptPolicyResponse(resp *http.Response) (res *DeleteScriptPol return res, errors.Wrap(defRes, "error") } -func decodeGetMapResponse(resp *http.Response) (res *Map, _ error) { +func decodeDownloadMapAssetResponse(resp *http.Response) (res DownloadMapAssetOK, _ error) { switch resp.StatusCode { case 200: // Code 200. @@ -2090,40 +2090,15 @@ func decodeGetMapResponse(resp *http.Response) (res *Map, _ error) { return res, errors.Wrap(err, "parse media type") } switch { - case ct == "application/json": - buf, err := io.ReadAll(resp.Body) + case ct == "application/octet-stream": + reader := resp.Body + b, err := io.ReadAll(reader) if err != nil { return res, err } - d := jx.DecodeBytes(buf) - var response Map - if err := func() error { - if err := response.Decode(d); err != nil { - return err - } - if err := d.Skip(); err != io.EOF { - return errors.New("unexpected trailing data") - } - return nil - }(); err != nil { - err = &ogenerrors.DecodeBodyError{ - ContentType: ct, - Body: buf, - Err: err, - } - return res, err - } - // Validate response. - if err := func() error { - if err := response.Validate(); err != nil { - return err - } - return nil - }(); err != nil { - return res, errors.Wrap(err, "validate") - } - return &response, nil + response := DownloadMapAssetOK{Data: bytes.NewReader(b)} + return response, nil default: return res, validate.InvalidContentType(ct) } @@ -2182,7 +2157,7 @@ func decodeGetMapResponse(resp *http.Response) (res *Map, _ error) { return res, errors.Wrap(defRes, "error") } -func decodeGetMapAssetLocationResponse(resp *http.Response) (res GetMapAssetLocationOK, _ error) { +func decodeGetMapResponse(resp *http.Response) (res *Map, _ error) { switch resp.StatusCode { case 200: // Code 200. @@ -2191,15 +2166,40 @@ func decodeGetMapAssetLocationResponse(resp *http.Response) (res GetMapAssetLoca return res, errors.Wrap(err, "parse media type") } switch { - case ct == "text/plain": - reader := resp.Body - b, err := io.ReadAll(reader) + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) if err != nil { return res, err } + d := jx.DecodeBytes(buf) - response := GetMapAssetLocationOK{Data: bytes.NewReader(b)} - return response, nil + var response Map + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil default: return res, validate.InvalidContentType(ct) } diff --git a/pkg/api/oas_response_encoders_gen.go b/pkg/api/oas_response_encoders_gen.go index 8d0f049..fb923c6 100644 --- a/pkg/api/oas_response_encoders_gen.go +++ b/pkg/api/oas_response_encoders_gen.go @@ -266,22 +266,8 @@ func encodeDeleteScriptPolicyResponse(response *DeleteScriptPolicyNoContent, w h return nil } -func encodeGetMapResponse(response *Map, w http.ResponseWriter, span trace.Span) error { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(200) - span.SetStatus(codes.Ok, http.StatusText(200)) - - e := new(jx.Encoder) - response.Encode(e) - if _, err := e.WriteTo(w); err != nil { - return errors.Wrap(err, "write") - } - - return nil -} - -func encodeGetMapAssetLocationResponse(response GetMapAssetLocationOK, w http.ResponseWriter, span trace.Span) error { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") +func encodeDownloadMapAssetResponse(response DownloadMapAssetOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/octet-stream") w.WriteHeader(200) span.SetStatus(codes.Ok, http.StatusText(200)) @@ -296,6 +282,20 @@ func encodeGetMapAssetLocationResponse(response GetMapAssetLocationOK, w http.Re return nil } +func encodeGetMapResponse(response *Map, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeGetMapfixResponse(response *Mapfix, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/pkg/api/oas_router_gen.go b/pkg/api/oas_router_gen.go index feb2cfc..1dc226d 100644 --- a/pkg/api/oas_router_gen.go +++ b/pkg/api/oas_router_gen.go @@ -592,9 +592,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } switch elem[0] { - case '/': // Prefix: "/location" + case '/': // Prefix: "/download" - if l := len("/location"); len(elem) >= l && elem[0:l] == "/location" { + if l := len("/download"); len(elem) >= l && elem[0:l] == "/download" { elem = elem[l:] } else { break @@ -604,7 +604,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Leaf node. switch r.Method { case "GET": - s.handleGetMapAssetLocationRequest([1]string{ + s.handleDownloadMapAssetRequest([1]string{ args[0], }, elemIsEscaped, w, r) default: @@ -2060,9 +2060,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } switch elem[0] { - case '/': // Prefix: "/location" + case '/': // Prefix: "/download" - if l := len("/location"); len(elem) >= l && elem[0:l] == "/location" { + if l := len("/download"); len(elem) >= l && elem[0:l] == "/download" { elem = elem[l:] } else { break @@ -2072,10 +2072,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { // Leaf node. switch method { case "GET": - r.name = GetMapAssetLocationOperation - r.summary = "Get location of asset" - r.operationID = "getMapAssetLocation" - r.pathPattern = "/maps/{MapID}/location" + r.name = DownloadMapAssetOperation + r.summary = "Download the map asset" + r.operationID = "downloadMapAsset" + r.pathPattern = "/maps/{MapID}/download" r.args = args r.count = 1 return r, true diff --git a/pkg/api/oas_schemas_gen.go b/pkg/api/oas_schemas_gen.go index 7646938..26b80ca 100644 --- a/pkg/api/oas_schemas_gen.go +++ b/pkg/api/oas_schemas_gen.go @@ -251,6 +251,20 @@ type DeleteScriptNoContent struct{} // DeleteScriptPolicyNoContent is response for DeleteScriptPolicy operation. type DeleteScriptPolicyNoContent struct{} +type DownloadMapAssetOK struct { + Data io.Reader +} + +// Read reads data from the Data reader. +// +// Kept to satisfy the io.Reader interface. +func (s DownloadMapAssetOK) Read(p []byte) (n int, err error) { + if s.Data == nil { + return 0, io.EOF + } + return s.Data.Read(p) +} + // Represents error object. // Ref: #/components/schemas/Error type Error struct { @@ -304,20 +318,6 @@ func (s *ErrorStatusCode) SetResponse(val Error) { s.Response = val } -type GetMapAssetLocationOK struct { - Data io.Reader -} - -// Read reads data from the Data reader. -// -// Kept to satisfy the io.Reader interface. -func (s GetMapAssetLocationOK) Read(p []byte) (n int, err error) { - if s.Data == nil { - return 0, io.EOF - } - return s.Data.Read(p) -} - // Ref: #/components/schemas/Map type Map struct { ID int64 `json:"ID"` diff --git a/pkg/api/oas_security_gen.go b/pkg/api/oas_security_gen.go index d2a0919..100c173 100644 --- a/pkg/api/oas_security_gen.go +++ b/pkg/api/oas_security_gen.go @@ -65,7 +65,7 @@ var operationRolesCookieAuth = map[string][]string{ CreateSubmissionAuditCommentOperation: []string{}, DeleteScriptOperation: []string{}, DeleteScriptPolicyOperation: []string{}, - GetMapAssetLocationOperation: []string{}, + DownloadMapAssetOperation: []string{}, GetOperationOperation: []string{}, ReleaseSubmissionsOperation: []string{}, SessionRolesOperation: []string{}, diff --git a/pkg/api/oas_server_gen.go b/pkg/api/oas_server_gen.go index dfe14e3..bc8d72e 100644 --- a/pkg/api/oas_server_gen.go +++ b/pkg/api/oas_server_gen.go @@ -196,18 +196,18 @@ type Handler interface { // // DELETE /script-policy/{ScriptPolicyID} DeleteScriptPolicy(ctx context.Context, params DeleteScriptPolicyParams) error + // DownloadMapAsset implements downloadMapAsset operation. + // + // Download the map asset. + // + // GET /maps/{MapID}/download + DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (DownloadMapAssetOK, error) // GetMap implements getMap operation. // // Retrieve map with ID. // // GET /maps/{MapID} GetMap(ctx context.Context, params GetMapParams) (*Map, error) - // GetMapAssetLocation implements getMapAssetLocation operation. - // - // Get location of asset. - // - // GET /maps/{MapID}/location - GetMapAssetLocation(ctx context.Context, params GetMapAssetLocationParams) (GetMapAssetLocationOK, error) // GetMapfix implements getMapfix operation. // // Retrieve map with ID. diff --git a/pkg/api/oas_unimplemented_gen.go b/pkg/api/oas_unimplemented_gen.go index 2cba41c..2c74330 100644 --- a/pkg/api/oas_unimplemented_gen.go +++ b/pkg/api/oas_unimplemented_gen.go @@ -294,6 +294,15 @@ func (UnimplementedHandler) DeleteScriptPolicy(ctx context.Context, params Delet return ht.ErrNotImplemented } +// DownloadMapAsset implements downloadMapAsset operation. +// +// Download the map asset. +// +// GET /maps/{MapID}/download +func (UnimplementedHandler) DownloadMapAsset(ctx context.Context, params DownloadMapAssetParams) (r DownloadMapAssetOK, _ error) { + return r, ht.ErrNotImplemented +} + // GetMap implements getMap operation. // // Retrieve map with ID. @@ -303,15 +312,6 @@ func (UnimplementedHandler) GetMap(ctx context.Context, params GetMapParams) (r return r, ht.ErrNotImplemented } -// GetMapAssetLocation implements getMapAssetLocation operation. -// -// Get location of asset. -// -// GET /maps/{MapID}/location -func (UnimplementedHandler) GetMapAssetLocation(ctx context.Context, params GetMapAssetLocationParams) (r GetMapAssetLocationOK, _ error) { - return r, ht.ErrNotImplemented -} - // GetMapfix implements getMapfix operation. // // Retrieve map with ID. From 749ea7e57dec77c7aeee3e6c2d4331957685606c Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 24 Jun 2025 05:19:48 -0700 Subject: [PATCH 5/6] submissions: change to proxy download --- pkg/roblox/asset_location.go | 18 ++++++++++++++++++ pkg/web_api/maps.go | 17 +++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/pkg/roblox/asset_location.go b/pkg/roblox/asset_location.go index 2b4a140..f5ce8ad 100644 --- a/pkg/roblox/asset_location.go +++ b/pkg/roblox/asset_location.go @@ -75,3 +75,21 @@ func (c *Client) GetAssetLocation(config GetAssetLatestRequest) (*AssetLocationI return &info, nil } + +func (c *Client) DownloadAsset(info *AssetLocationInfo) (io.Reader, error) { + req, err := http.NewRequest("GET", info.Location, nil) + if err != nil { + return nil, GetError("RequestCreationError: " + err.Error()) + } + + resp, err := c.HttpClient.Do(req) + if err != nil { + return nil, GetError("RequestError: " + err.Error()) + } + + if resp.StatusCode != http.StatusOK { + return nil, GetError(fmt.Sprintf("ResponseError: status code %d", resp.StatusCode)) + } + + return resp.Body, nil +} diff --git a/pkg/web_api/maps.go b/pkg/web_api/maps.go index a6e806b..e40e587 100644 --- a/pkg/web_api/maps.go +++ b/pkg/web_api/maps.go @@ -2,7 +2,6 @@ package web_api import ( "context" - "strings" "git.itzana.me/strafesnet/maps-service/pkg/api" "git.itzana.me/strafesnet/maps-service/pkg/model" @@ -73,12 +72,12 @@ func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.M }, nil } -// GetMapAssetLocation invokes getMapAssetLocation operation. +// DownloadMapAsset invokes downloadMapAsset operation. // -// Get location of map asset. +// Download the map asset. // -// GET /maps/{MapID}/location -func (svc *Service) GetMapAssetLocation(ctx context.Context, params api.GetMapAssetLocationParams) (ok api.GetMapAssetLocationOK, err error) { +// GET /maps/{MapID}/download +func (svc *Service) DownloadMapAsset(ctx context.Context, params api.DownloadMapAssetParams) (ok api.DownloadMapAssetOK, err error) { userInfo, success := ctx.Value("UserInfo").(UserInfoHandle) if !success { return ok, ErrUserInfo @@ -107,6 +106,12 @@ func (svc *Service) GetMapAssetLocation(ctx context.Context, params api.GetMapAs return ok, err } - ok.Data = strings.NewReader(info.Location) + // download the complete file + asset, err := svc.roblox.DownloadAsset(info) + if err != nil{ + return ok, err + } + + ok.Data = asset return ok, nil } From 3e353b2ec6a317b0baeb1c670c54f58f4032d8c9 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 24 Jun 2025 05:44:24 -0700 Subject: [PATCH 6/6] web: change to proxy download --- web/src/app/maps/[mapId]/page.tsx | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/web/src/app/maps/[mapId]/page.tsx b/web/src/app/maps/[mapId]/page.tsx index 1989152..5f56ff3 100644 --- a/web/src/app/maps/[mapId]/page.tsx +++ b/web/src/app/maps/[mapId]/page.tsx @@ -45,7 +45,6 @@ export default function MapDetails() { const [error, setError] = useState(null); const [copySuccess, setCopySuccess] = useState(false); const [roles, setRoles] = useState(RolesConstants.Empty); - const [downloading, setDownloading] = useState(false); const [mapfixes, setMapfixes] = useState([]); useTitle(map ? `${map.DisplayName}` : 'Loading Map...'); @@ -159,28 +158,6 @@ export default function MapDetails() { setCopySuccess(true); }; - - const handleDownload = async () => { - setDownloading(true); - try { - // Fetch the download URL - const res = await fetch(`/api/maps/${mapId}/location`); - if (!res.ok) throw new Error('Failed to fetch download location'); - - const location = await res.text(); - - // open in new window - window.open(location.trim(), '_blank'); - - } catch (err) { - console.error('Download error:', err); - // Optional: Show user-friendly error message - alert('Download failed. Please try again.'); - } finally { - setDownloading(false); - } - }; - const handleCloseSnackbar = () => { setCopySuccess(false); }; @@ -318,12 +295,13 @@ export default function MapDetails() { Download - +