Compare commits

...

6 Commits

15 changed files with 124 additions and 87 deletions

4
Cargo.lock generated

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

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

@ -50,7 +50,7 @@ type Invoker interface {
//
// (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
// 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.
//
// POST /operations/{OperationID}/failed
// POST /operations/{OperationID}/status/operation-failed
func (c *Client) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error {
_, err := c.sendActionOperationFailed(ctx, params)
return err
@ -478,7 +478,7 @@ func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOpe
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionOperationFailed"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"),
semconv.HTTPRouteKey.String("/operations/{OperationID}/status/operation-failed"),
}
// Run stopwatch.
@ -530,7 +530,7 @@ func (c *Client) sendActionOperationFailed(ctx context.Context, params ActionOpe
}
pathParts[1] = encoded
}
pathParts[2] = "/failed"
pathParts[2] = "/status/operation-failed"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"

@ -485,14 +485,14 @@ func (s *Server) handleActionMapfixValidatedRequest(args [1]string, argsEscaped
//
// (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) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionOperationFailed"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/operations/{OperationID}/failed"),
semconv.HTTPRouteKey.String("/operations/{OperationID}/status/operation-failed"),
}
// Start a span for this request.

@ -242,9 +242,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break
}
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:]
} else {
break
@ -817,9 +817,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break
}
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:]
} else {
break
@ -832,7 +832,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
r.name = ActionOperationFailedOperation
r.summary = "(Internal endpoint) Fail an operation and write a StatusMessage"
r.operationID = "actionOperationFailed"
r.pathPattern = "/operations/{OperationID}/failed"
r.pathPattern = "/operations/{OperationID}/status/operation-failed"
r.args = args
r.count = 1
return r, true

@ -30,7 +30,7 @@ type Handler interface {
//
// (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
// 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.
//
// POST /operations/{OperationID}/failed
// POST /operations/{OperationID}/status/operation-failed
func (UnimplementedHandler) ActionOperationFailed(ctx context.Context, params ActionOperationFailedParams) error {
return ht.ErrNotImplemented
}

@ -3,6 +3,7 @@ package service_internal
import (
"context"
"errors"
"fmt"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
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 {
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{
MapfixID: mapfix.ID,
}, nil

@ -12,7 +12,7 @@ import (
//
// 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) {
pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusFailed)

@ -3,6 +3,7 @@ package service_internal
import (
"context"
"errors"
"fmt"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
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 {
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{
SubmissionID: submission.ID,
}, nil

@ -7,7 +7,7 @@ edition = "2021"
submissions-api = { path = "api", features = ["internal"], default-features = false, registry = "strafesnet" }
async-nats = "0.40.0"
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_dom_weak = { version = "2.9.0", 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{
ModelVersionsPage(rbx_asset::cookie::PageError),
EmptyVersionsPage,
WrongCreatorType,
CreatorTypeMustBeUser(rbx_asset::cookie::CreatorType),
ModelDetails(rbx_asset::cookie::GetError),
ModelInfoDownload(rbx_asset::cookie::GetAssetV2Error),
ModelFileDownload(rbx_asset::cookie::GetError),
NoLocations,
ModelFileDecode(ReadDomError),
GetMapInfo(GetMapInfoError),
ParseGameID(ParseGameIDError),
@ -32,28 +35,32 @@ pub struct CreateResult{
}
impl crate::message_handler::MessageHandler{
pub async fn create_inner(&self,create_info:CreateRequest)->Result<CreateResult,Error>{
// discover the latest asset version
let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{
asset_id:create_info.ModelID,
cursor:None
}).await.map_err(Error::ModelVersionsPage)?;
// discover asset creator
let creator_fut=async{
self.cookie_context.get_asset_details(
rbx_asset::cookie::GetAssetDetailsRequest{asset_id:create_info.ModelID}
).await.map_err(Error::ModelDetails)
};
// grab version info
let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?;
// download the map model
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"{
return Err(Error::WrongCreatorType);
let location=asset_info.info.locations.first().ok_or(Error::NoLocations)?;
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!)
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)?;
Ok(CreateResult{
AssetOwner:asset_creator_id as i64,
AssetOwner:details.Creator.Id as i64,
DisplayName:display_name.unwrap_or_default().to_owned(),
Creator:creator.unwrap_or_default().to_owned(),
GameID:game_id as i32,

@ -4,8 +4,8 @@ use crate::create::CreateRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
Create(crate::create::Error),
ApiActionMapfixCreate(submissions_api::Error),
ApiActionOperationFailed(submissions_api::Error),
}
impl std::fmt::Display for Error{
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 crate::message_handler::MessageHandler{
pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),Error>{
let create_result=self.create_inner(CreateRequest{
async fn create_mapfix_inner(&self,create_info:CreateMapfixRequest)->Result<(),Error>{
// call deduplicated inner code
let create_request=self.create_inner(CreateRequest{
ModelID:create_info.ModelID,
}).await;
}).await.map_err(Error::Create)?;
match create_result{
Ok(create_request)=>{
// call create on api
self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{
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,
TargetAssetID:create_info.TargetAssetID,
}).await.map_err(Error::ApiActionMapfixCreate)?;
},
Err(e)=>{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:create_info.OperationID,
StatusMessage:format!("{e}"),
}).await.map_err(Error::ApiActionOperationFailed)?;
},
// call create on api
self.api.create_mapfix(submissions_api::types::CreateMapfixRequest{
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,
TargetAssetID:create_info.TargetAssetID,
}).await.map_err(Error::ApiActionMapfixCreate)?;
Ok(())
}
pub async fn create_mapfix(&self,create_info:CreateMapfixRequest)->Result<(),submissions_api::Error>{
let operation_id=create_info.OperationID;
let create_result=self.create_mapfix_inner(create_info).await;
if let Err(e)=create_result{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:operation_id,
StatusMessage:format!("{e}"),
}).await?;
}
Ok(())

@ -4,8 +4,8 @@ use crate::create::CreateRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
Create(crate::create::Error),
ApiActionSubmissionCreate(submissions_api::Error),
ApiActionOperationFailed(submissions_api::Error),
}
impl std::fmt::Display for Error{
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 crate::message_handler::MessageHandler{
pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{
let create_result=self.create_inner(CreateRequest{
async fn create_submission_inner(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{
let create_request=self.create_inner(CreateRequest{
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(create_request)=>{
// 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)?;
},
Err(e)=>{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:create_info.OperationID,
StatusMessage:format!("{e}"),
}).await.map_err(Error::ApiActionOperationFailed)?;
},
Ok(())
}
pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),submissions_api::Error>{
let operation_id=create_info.OperationID;
let create_result=self.create_submission_inner(create_info).await;
if let Err(e)=create_result{
self.api.action_operation_failed(submissions_api::types::ActionOperationFailedRequest{
OperationID:operation_id,
StatusMessage:format!("{e}"),
}).await?;
}
Ok(())

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