Compare commits

...

6 Commits

15 changed files with 124 additions and 87 deletions

4
Cargo.lock generated

@ -1297,9 +1297,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_asset" name = "rbx_asset"
version = "0.2.5" version = "0.3.3"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "dcf243f46bd41b3880a27278177a3f9996f95ab231d9a04345ad9dd381c3a54a" checksum = "91722b37549ded270f39556194ca03d03e08bd70674d239ec845765ed9e42b7d"
dependencies = [ dependencies = [
"chrono", "chrono",
"flate2", "flate2",

@ -121,7 +121,7 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/operations/{OperationID}/failed: /operations/{OperationID}/status/operation-failed:
post: post:
summary: (Internal endpoint) Fail an operation and write a StatusMessage summary: (Internal endpoint) Fail an operation and write a StatusMessage
operationId: actionOperationFailed operationId: actionOperationFailed

@ -50,7 +50,7 @@ type Invoker interface {
// //
// (Internal endpoint) Fail an operation and write a StatusMessage. // (Internal endpoint) Fail an operation and write a StatusMessage.
// //
// POST /operations/{OperationID}/failed // POST /operations/{OperationID}/status/operation-failed
ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error
// ActionSubmissionAccepted invokes actionSubmissionAccepted operation. // ActionSubmissionAccepted invokes actionSubmissionAccepted operation.
// //
@ -468,7 +468,7 @@ func (c *Client) sendActionMapfixValidated(ctx context.Context, params ActionMap
// //
// (Internal endpoint) Fail an operation and write a StatusMessage. // (Internal endpoint) Fail an operation and write a StatusMessage.
// //
// POST /operations/{OperationID}/failed // POST /operations/{OperationID}/status/operation-failed
func (c *Client) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error { func (c *Client) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error {
_, err := c.sendActionOperationFailed(ctx, params) _, err := c.sendActionOperationFailed(ctx, params)
return err return err
@ -478,7 +478,7 @@ func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOpe
otelAttrs := []attribute.KeyValue{ otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionOperationFailed"), otelogen.OperationID("actionOperationFailed"),
semconv.HTTPRequestMethodKey.String("POST"), semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"), semconv.HTTPRouteKey.String("/operations/{OperationID}/status/operation-failed"),
} }
// Run stopwatch. // Run stopwatch.
@ -530,7 +530,7 @@ func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOpe
} }
pathParts[1] = encoded pathParts[1] = encoded
} }
pathParts[2] = "/failed" pathParts[2] = "/status/operation-failed"
uri.AddPathParts(u, pathParts[:]...) uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams" stage = "EncodeQueryParams"

@ -485,14 +485,14 @@ func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped
// //
// (Internal endpoint) Fail an operation and write a StatusMessage. // (Internal endpoint) Fail an operation and write a StatusMessage.
// //
// POST /operations/{OperationID}/failed // POST /operations/{OperationID}/status/operation-failed
func (s *Server) handleActionOperationFailedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { func (s *Server) handleActionOperationFailedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w} statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter w = statusWriter
otelAttrs := []attribute.KeyValue{ otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionOperationFailed"), otelogen.OperationID("actionOperationFailed"),
semconv.HTTPRequestMethodKey.String("POST"), semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"), semconv.HTTPRouteKey.String("/operations/{OperationID}/status/operation-failed"),
} }
// Start a span for this request. // Start a span for this request.

@ -242,9 +242,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case '/': // Prefix: "/failed" case '/': // Prefix: "/status/operation-failed"
if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" { if l := len("/status/operation-failed"); len(elem) >= l && elem[0:l] == "/status/operation-failed" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@ -817,9 +817,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case '/': // Prefix: "/failed" case '/': // Prefix: "/status/operation-failed"
if l := len("/failed"); len(elem) >= l && elem[0:l] == "/failed" { if l := len("/status/operation-failed"); len(elem) >= l && elem[0:l] == "/status/operation-failed" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@ -832,7 +832,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionOperationFailedOperation r.name = ActionOperationFailedOperation
r.summary = "(Internal endpoint) Fail an operation and write a StatusMessage" r.summary = "(Internal endpoint) Fail an operation and write a StatusMessage"
r.operationID = "actionOperationFailed" r.operationID = "actionOperationFailed"
r.pathPattern = "/operations/{OperationID}/failed" r.pathPattern = "/operations/{OperationID}/status/operation-failed"
r.args = args r.args = args
r.count = 1 r.count = 1
return r, true return r, true

@ -30,7 +30,7 @@ type Handler interface {
// //
// (Internal endpoint) Fail an operation and write a StatusMessage. // (Internal endpoint) Fail an operation and write a StatusMessage.
// //
// POST /operations/{OperationID}/failed // POST /operations/{OperationID}/status/operation-failed
ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error
// ActionSubmissionAccepted implements actionSubmissionAccepted operation. // ActionSubmissionAccepted implements actionSubmissionAccepted operation.
// //

@ -44,7 +44,7 @@ func (UnimplementedHandler) ActionMapfixValidated(ctx context.Context, params Ac
// //
// (Internal endpoint) Fail an operation and write a StatusMessage. // (Internal endpoint) Fail an operation and write a StatusMessage.
// //
// POST /operations/{OperationID}/failed // POST /operations/{OperationID}/status/operation-failed
func (UnimplementedHandler) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error { func (UnimplementedHandler) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }

@ -3,6 +3,7 @@ package service_internal
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal" internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
@ -125,6 +126,16 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
if err != nil { if err != nil {
return nil, err return nil, err
} }
// mark the operation as completed and provide the path
pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusCompleted)
pmap.Add("path", fmt.Sprintf("/mapfixes/%d", mapfix.ID))
err = svc.DB.Operations().Update(ctx, request.OperationID, pmap)
if err != nil {
return nil, err
}
return &internal.MapfixID{ return &internal.MapfixID{
MapfixID: mapfix.ID, MapfixID: mapfix.ID,
}, nil }, nil

@ -12,7 +12,7 @@ import (
// //
// Fail the specified OperationID with a StatusMessage. // Fail the specified OperationID with a StatusMessage.
// //
// POST /operations/{OperationID}/failed // POST /operations/{OperationID}/status/operation-failed
func (svc *Service) ActionOperationFailed(ctx context.Context, params internal.ActionOperationFailedParams) (error) { func (svc *Service) ActionOperationFailed(ctx context.Context, params internal.ActionOperationFailedParams) (error) {
pmap := datastore.Optional() pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusFailed) pmap.Add("status_id", model.OperationStatusFailed)

@ -3,6 +3,7 @@ package service_internal
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"git.itzana.me/strafesnet/maps-service/pkg/datastore" "git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal" internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
@ -124,6 +125,16 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
if err != nil { if err != nil {
return nil, err return nil, err
} }
// mark the operation as completed and provide the path
pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusCompleted)
pmap.Add("path", fmt.Sprintf("/submissions/%d", submission.ID))
err = svc.DB.Operations().Update(ctx, request.OperationID, pmap)
if err != nil {
return nil, err
}
return &internal.SubmissionID{ return &internal.SubmissionID{
SubmissionID: submission.ID, SubmissionID: submission.ID,
}, nil }, nil

@ -7,7 +7,7 @@ edition = "2021"
submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" } submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" }
async-nats = "0.40.0" async-nats = "0.40.0"
futures = "0.3.31" futures = "0.3.31"
rbx_asset = { version = "0.2.5", registry = "strafesnet" } rbx_asset = { version = "0.3.3", registry = "strafesnet" }
rbx_binary = { version = "0.7.4", registry = "strafesnet"} rbx_binary = { version = "0.7.4", registry = "strafesnet"}
rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"} rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"}
rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"} rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"}

@ -5,8 +5,11 @@ use crate::rbx_util::{get_mapinfo,read_dom,MapInfo,ReadDomError,GetMapInfoError,
pub enum Error{ pub enum Error{
ModelVersionsPage(rbx_asset::cookie::PageError), ModelVersionsPage(rbx_asset::cookie::PageError),
EmptyVersionsPage, EmptyVersionsPage,
WrongCreatorType, CreatorTypeMustBeUser(rbx_asset::cookie::CreatorType),
ModelDetails(rbx_asset::cookie::GetError),
ModelInfoDownload(rbx_asset::cookie::GetAssetV2Error),
ModelFileDownload(rbx_asset::cookie::GetError), ModelFileDownload(rbx_asset::cookie::GetError),
NoLocations,
ModelFileDecode(ReadDomError), ModelFileDecode(ReadDomError),
GetMapInfo(GetMapInfoError), GetMapInfo(GetMapInfoError),
ParseGameID(ParseGameIDError), ParseGameID(ParseGameIDError),
@ -32,28 +35,32 @@ pub struct CreateResult{
} }
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{ pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{
// discover the latest asset version // discover asset creator
let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{ let creator_fut=async{
asset_id:create_info.ModelID, self.cookie_context.get_asset_details(
cursor:None rbx_asset::cookie::GetAssetDetailsRequest{asset_id:create_info.ModelID}
}).await.map_err(Error::ModelVersionsPage)?; ).await.map_err(Error::ModelDetails)
};
// grab version info // download the map model
let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?; let asset_fut=async{
let asset_info=self.cookie_context.get_asset_v2(rbx_asset::cookie::GetAssetRequest{
asset_id:create_info.ModelID,
version:None,
}).await.map_err(Error::ModelInfoDownload)?;
if first_version.creatorType!="User"{ let location=asset_info.info.locations.first().ok_or(Error::NoLocations)?;
return Err(Error::WrongCreatorType); let data=self.cookie_context.get_asset_v2_download(location).await.map_err(Error::ModelFileDownload)?;
Ok((asset_info.version,data))
};
let (details,(asset_version,model_data))=tokio::try_join!(creator_fut,asset_fut)?;
if details.Creator.CreatorType!=rbx_asset::cookie::CreatorType::User{
return Err(Error::CreatorTypeMustBeUser(details.Creator.CreatorType));
} }
let asset_creator_id=first_version.creatorTargetId;
let asset_version=first_version.assetVersionNumber;
// download the map model version
let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:create_info.ModelID,
version:Some(asset_version),
}).await.map_err(Error::ModelFileDownload)?;
// decode dom (slow!) // decode dom (slow!)
let dom=read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?; let dom=read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?;
@ -67,7 +74,7 @@ impl crate::message_handler::MessageHandler{
let game_id=game_id.map_err(Error::ParseGameID)?; let game_id=game_id.map_err(Error::ParseGameID)?;
Ok(CreateResult{ Ok(CreateResult{
AssetOwner:asset_creator_id as i64, AssetOwner:details.Creator.Id as i64,
DisplayName:display_name.unwrap_or_default().to_owned(), DisplayName:display_name.unwrap_or_default().to_owned(),
Creator:creator.unwrap_or_default().to_owned(), Creator:creator.unwrap_or_default().to_owned(),
GameID:game_id as i32, GameID:game_id as i32,

@ -4,8 +4,8 @@ use crate::create::CreateRequest;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
Create(crate::create::Error),
ApiActionMapfixCreate(submissions_api::Error), ApiActionMapfixCreate(submissions_api::Error),
ApiActionOperationFailed(submissions_api::Error),
} }
impl std::fmt::Display for Error{ impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -15,31 +15,36 @@ impl std::fmt::Display for Error{
impl std::error::Error for Error{} impl std::error::Error for Error{}
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),Error>{ async fn create_mapfix_inner(&self,create_info:CreateMapfixRequest)->Result<(),Error>{
let create_result=self.create_inner(CreateRequest{ // call deduplicated inner code
let create_request=self.create_inner(CreateRequest{
ModelID:create_info.ModelID, ModelID:create_info.ModelID,
}).await; }).await.map_err(Error::Create)?;
match create_result{ // call create on api
Ok(create_request)=>{ self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{
// call create on api OperationID:create_info.OperationID,
self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{ AssetOwner:create_request.AssetOwner,
OperationID:create_info.OperationID, DisplayName:create_request.DisplayName.as_str(),
AssetOwner:create_request.AssetOwner, Creator:create_request.Creator.as_str(),
DisplayName:create_request.DisplayName.as_str(), GameID:create_request.GameID,
Creator:create_request.Creator.as_str(), AssetID:create_info.ModelID,
GameID:create_request.GameID, AssetVersion:create_request.AssetVersion,
AssetID:create_info.ModelID, TargetAssetID:create_info.TargetAssetID,
AssetVersion:create_request.AssetVersion, }).await.map_err(Error::ApiActionMapfixCreate)?;
TargetAssetID:create_info.TargetAssetID,
}).await.map_err(Error::ApiActionMapfixCreate)?; Ok(())
}, }
Err(e)=>{ pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),submissions_api::Error>{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{ let operation_id=create_info.OperationID;
OperationID:create_info.OperationID,
StatusMessage:format!("{e}"), let create_result=self.create_mapfix_inner(create_info).await;
}).await.map_err(Error::ApiActionOperationFailed)?;
}, if let Err(e)=create_result{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:operation_id,
StatusMessage:format!("{e}"),
}).await?;
} }
Ok(()) Ok(())

@ -4,8 +4,8 @@ use crate::create::CreateRequest;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
Create(crate::create::Error),
ApiActionSubmissionCreate(submissions_api::Error), ApiActionSubmissionCreate(submissions_api::Error),
ApiActionOperationFailed(submissions_api::Error),
} }
impl std::fmt::Display for Error{ impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -15,30 +15,33 @@ impl std::fmt::Display for Error{
impl std::error::Error for Error{} impl std::error::Error for Error{}
impl crate::message_handler::MessageHandler{ impl crate::message_handler::MessageHandler{
pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{ async fn create_submission_inner(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{
let create_result=self.create_inner(CreateRequest{ let create_request=self.create_inner(CreateRequest{
ModelID:create_info.ModelID, ModelID:create_info.ModelID,
}).await; }).await.map_err(Error::Create)?;
// call create on api
self.api.create_submission(submissions_api::types::CreateSubmissionRequest{
OperationID:create_info.OperationID,
AssetOwner:create_request.AssetOwner,
DisplayName:create_request.DisplayName.as_str(),
Creator:create_request.Creator.as_str(),
GameID:create_request.GameID,
AssetID:create_info.ModelID,
AssetVersion:create_request.AssetVersion,
}).await.map_err(Error::ApiActionSubmissionCreate)?;
match create_result{ Ok(())
Ok(create_request)=>{ }
// call create on api pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),submissions_api::Error>{
self.api.create_submission(submissions_api::types::CreateSubmissionRequest{ let operation_id=create_info.OperationID;
OperationID:create_info.OperationID,
AssetOwner:create_request.AssetOwner, let create_result=self.create_submission_inner(create_info).await;
DisplayName:create_request.DisplayName.as_str(),
Creator:create_request.Creator.as_str(), if let Err(e)=create_result{
GameID:create_request.GameID, self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
AssetID:create_info.ModelID, OperationID:operation_id,
AssetVersion:create_request.AssetVersion, StatusMessage:format!("{e}"),
}).await.map_err(Error::ApiActionSubmissionCreate)?; }).await?;
},
Err(e)=>{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:create_info.OperationID,
StatusMessage:format!("{e}"),
}).await.map_err(Error::ApiActionOperationFailed)?;
},
} }
Ok(()) Ok(())

@ -5,8 +5,8 @@ pub enum HandleMessageError{
DoubleAck(async_nats::Error), DoubleAck(async_nats::Error),
Json(serde_json::Error), Json(serde_json::Error),
UnknownSubject(String), UnknownSubject(String),
CreateMapfix(crate::create_mapfix::Error), CreateMapfix(submissions_api::Error),
CreateSubmission(crate::create_submission::Error), CreateSubmission(submissions_api::Error),
UploadMapfix(crate::upload_mapfix::Error), UploadMapfix(crate::upload_mapfix::Error),
UploadSubmission(crate::upload_submission::Error), UploadSubmission(crate::upload_submission::Error),
ValidateMapfix(crate::validate_mapfix::Error), ValidateMapfix(crate::validate_mapfix::Error),