From 732598266cae21825e7e9fd0827d5a563309f722 Mon Sep 17 00:00:00 2001 From: Quaternions <krakow20@gmail.com> Date: Tue, 1 Apr 2025 13:32:37 -0700 Subject: [PATCH] submissions: mapfixes --- pkg/model/mapfix.go | 37 ++ pkg/model/nats.go | 7 + pkg/service/mapfixes.go | 598 +++++++++++++++++++++++++++++++ pkg/service_internal/mapfixes.go | 61 ++++ 4 files changed, 703 insertions(+) create mode 100644 pkg/model/mapfix.go create mode 100644 pkg/service/mapfixes.go create mode 100644 pkg/service_internal/mapfixes.go diff --git a/pkg/model/mapfix.go b/pkg/model/mapfix.go new file mode 100644 index 0000000..09e254a --- /dev/null +++ b/pkg/model/mapfix.go @@ -0,0 +1,37 @@ +package model + +import "time" + +type MapfixStatus int32 + +const ( + // Phase: Final MapfixStatus + MapfixStatusRejected MapfixStatus = 8 + MapfixStatusUploaded MapfixStatus = 7 // uploaded to the group, final status for mapfixes + + // Phase: Testing + MapfixStatusUploading MapfixStatus = 6 + MapfixStatusValidated MapfixStatus = 5 + MapfixStatusValidating MapfixStatus = 4 + MapfixStatusAccepted MapfixStatus = 3 // pending script review, can re-trigger validation + + // Phase: Creation + MapfixStatusChangesRequested MapfixStatus = 2 + MapfixStatusSubmitted MapfixStatus = 1 + MapfixStatusUnderConstruction MapfixStatus = 0 +) + +type Mapfix struct { + ID int64 `gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + Submitter int64 // UserID + AssetID int64 + AssetVersion int64 + ValidatedAssetID int64 + ValidatedAssetVersion int64 + Completed bool // Has this version of the map been completed at least once on maptest + TargetAssetID int64 // where to upload map fix. if the TargetAssetID is 0, it's a new map. + StatusID MapfixStatus + StatusMessage string +} diff --git a/pkg/model/nats.go b/pkg/model/nats.go index 60114b0..6add04f 100644 --- a/pkg/model/nats.go +++ b/pkg/model/nats.go @@ -13,6 +13,13 @@ type ValidateSubmissionRequest struct { ValidatedModelID *int64 // optional value } +type ValidateMapfixRequest struct { + MapfixID int64 + ModelID int64 + ModelVersion int64 + ValidatedModelID *int64 // optional value +} + // Create a new map type UploadSubmissionRequest struct { SubmissionID int64 diff --git a/pkg/service/mapfixes.go b/pkg/service/mapfixes.go new file mode 100644 index 0000000..f318661 --- /dev/null +++ b/pkg/service/mapfixes.go @@ -0,0 +1,598 @@ +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "git.itzana.me/strafesnet/maps-service/pkg/api" + "git.itzana.me/strafesnet/maps-service/pkg/datastore" + "git.itzana.me/strafesnet/maps-service/pkg/model" +) + +var( + CreationPhaseMapfixesLimit = 20 + CreationPhaseMapfixStatuses = []model.MapfixStatus{ + model.MapfixStatusChangesRequested, + model.MapfixStatusSubmitted, + model.MapfixStatusUnderConstruction, + } + // prevent two mapfixes with same asset id + ActiveMapfixStatuses = []model.MapfixStatus{ + model.MapfixStatusUploading, + model.MapfixStatusValidated, + model.MapfixStatusValidating, + model.MapfixStatusAccepted, + model.MapfixStatusChangesRequested, + model.MapfixStatusSubmitted, + model.MapfixStatusUnderConstruction, + } + // limit mapfixes in the pipeline to one per target map + ActiveAcceptedMapfixStatuses = []model.MapfixStatus{ + model.MapfixStatusUploading, + model.MapfixStatusValidated, + model.MapfixStatusValidating, + model.MapfixStatusAccepted, + } +) + +var ( + ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20") + ErrActiveMapfixSameAssetID = errors.New("There is an active mapfix with the same AssetID") + ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix with the same TargetAssetID") + ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied) +) + +// POST /mapfixes +func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixCreate) (*api.ID, error) { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return nil, ErrUserInfo + } + + userId, err := userInfo.GetUserID() + if err != nil { + return nil, err + } + + // Check if user's mapfixes in the creation phase exceeds the limit + { + filter := datastore.Optional() + filter.Add("submitter", int64(userId)) + filter.Add("status_id", CreationPhaseMapfixStatuses) + creation_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ + Number: 1, + Size: int32(CreationPhaseMapfixesLimit), + },datastore.ListSortDisabled) + if err != nil { + return nil, err + } + + if CreationPhaseMapfixesLimit <= len(creation_mapfixes) { + return nil, ErrCreationPhaseMapfixesLimit + } + } + + // Check if an active mapfix with the same asset id exists + { + filter := datastore.Optional() + filter.Add("asset_id", request.AssetID) + filter.Add("asset_version", request.AssetVersion) + filter.Add("status_id", ActiveMapfixStatuses) + active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ + Number: 1, + Size: 1, + },datastore.ListSortDisabled) + if err != nil { + return nil, err + } + if len(active_mapfixes) != 0{ + return nil, ErrActiveMapfixSameAssetID + } + } + + mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{ + ID: 0, + Submitter: int64(userId), + AssetID: request.AssetID, + AssetVersion: request.AssetVersion, + Completed: false, + TargetAssetID: request.TargetAssetID, + StatusID: model.MapfixStatusUnderConstruction, + }) + if err != nil { + return nil, err + } + return &api.ID{ + ID: mapfix.ID, + }, nil +} + +// GetMapfix implements getMapfix operation. +// +// Retrieve map with ID. +// +// GET /mapfixes/{MapfixID} +func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) (*api.Mapfix, error) { + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return nil, err + } + return &api.Mapfix{ + ID: mapfix.ID, + CreatedAt: mapfix.CreatedAt.Unix(), + UpdatedAt: mapfix.UpdatedAt.Unix(), + Submitter: int64(mapfix.Submitter), + AssetID: int64(mapfix.AssetID), + AssetVersion: int64(mapfix.AssetVersion), + Completed: mapfix.Completed, + TargetAssetID: int64(mapfix.TargetAssetID), + StatusID: int32(mapfix.StatusID), + StatusMessage: mapfix.StatusMessage, + }, nil +} + +// ListMapfixes implements listMapfixes operation. +// +// Get list of mapfixes. +// +// GET /mapfixes +func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesParams) ([]api.Mapfix, error) { + filter := datastore.Optional() + + sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled))) + + items, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ + Number: params.Page, + Size: params.Limit, + },sort) + if err != nil { + return nil, err + } + + var resp []api.Mapfix + for _, item := range items { + resp = append(resp, api.Mapfix{ + ID: item.ID, + CreatedAt: item.CreatedAt.Unix(), + UpdatedAt: item.UpdatedAt.Unix(), + Submitter: int64(item.Submitter), + AssetID: int64(item.AssetID), + AssetVersion: int64(item.AssetVersion), + Completed: item.Completed, + TargetAssetID: int64(item.TargetAssetID), + StatusID: int32(item.StatusID), + }) + } + + return resp, nil +} + +// PatchMapfixCompleted implements patchMapfixCompleted operation. +// +// Retrieve map with ID. +// +// POST /mapfixes/{MapfixID}/completed +func (svc *Service) SetMapfixCompleted(ctx context.Context, params api.SetMapfixCompletedParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMaptest() + if err != nil { + return err + } + // check if caller has MaptestGame role (request must originate from a maptest roblox game) + if !has_role { + return ErrPermissionDeniedNeedRoleMaptest + } + + pmap := datastore.Optional() + pmap.Add("completed", true) + return svc.DB.Mapfixes().Update(ctx, params.MapfixID, pmap) +} + +// UpdateMapfixModel implements patchMapfixModel operation. +// +// Update model following role restrictions. +// +// POST /mapfixes/{MapfixID}/model +func (svc *Service) UpdateMapfixModel(ctx context.Context, params api.UpdateMapfixModelParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + // read mapfix (this could be done with a transaction WHERE clause) + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + + has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) + if err != nil { + return err + } + // check if caller is the submitter + if !has_role { + return ErrPermissionDeniedNotSubmitter + } + + // check if Status is ChangesRequested|Submitted|UnderConstruction + pmap := datastore.Optional() + pmap.AddNotNil("asset_id", params.ModelID) + pmap.AddNotNil("asset_version", params.VersionID) + //always reset completed when model changes + pmap.Add("completed", false) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction}, pmap) +} + +// ActionMapfixReject invokes actionMapfixReject operation. +// +// Role Reviewer changes status from Submitted -> Rejected. +// +// POST /mapfixes/{MapfixID}/status/reject +func (svc *Service) ActionMapfixReject(ctx context.Context, params api.ActionMapfixRejectParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixReview() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusRejected) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) +} + +// ActionMapfixRequestChanges invokes actionMapfixRequestChanges operation. +// +// Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested. +// +// POST /mapfixes/{MapfixID}/status/request-changes +func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params api.ActionMapfixRequestChangesParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixReview() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusChangesRequested) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated, model.MapfixStatusAccepted, model.MapfixStatusSubmitted}, smap) +} + +// ActionMapfixRevoke invokes actionMapfixRevoke operation. +// +// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction. +// +// POST /mapfixes/{MapfixID}/status/revoke +func (svc *Service) ActionMapfixRevoke(ctx context.Context, params api.ActionMapfixRevokeParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + // read mapfix (this could be done with a transaction WHERE clause) + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + + has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) + if err != nil { + return err + } + // check if caller is the submitter + if !has_role { + return ErrPermissionDeniedNotSubmitter + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusUnderConstruction) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted, model.MapfixStatusChangesRequested}, smap) +} + +// ActionMapfixSubmit invokes actionMapfixSubmit operation. +// +// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted. +// +// POST /mapfixes/{MapfixID}/status/submit +func (svc *Service) ActionMapfixSubmit(ctx context.Context, params api.ActionMapfixSubmitParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + // read mapfix (this could be done with a transaction WHERE clause) + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + + has_role, err := userInfo.IsSubmitter(uint64(mapfix.Submitter)) + if err != nil { + return err + } + // check if caller is the submitter + if !has_role { + return ErrPermissionDeniedNotSubmitter + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusSubmitted) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}, smap) +} + +// ActionMapfixTriggerUpload invokes actionMapfixTriggerUpload operation. +// +// Role Admin changes status from Validated -> Uploading. +// +// POST /mapfixes/{MapfixID}/status/trigger-upload +func (svc *Service) ActionMapfixTriggerUpload(ctx context.Context, params api.ActionMapfixTriggerUploadParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixUpload() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapUpload + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusUploading) + mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidated}, smap) + if err != nil { + return err + } + + // this is a map fix + upload_fix_request := model.UploadMapfixRequest{ + MapfixID: mapfix.ID, + ModelID: mapfix.ValidatedAssetID, + ModelVersion: mapfix.ValidatedAssetVersion, + TargetAssetID: mapfix.TargetAssetID, + } + + j, err := json.Marshal(upload_fix_request) + if err != nil { + return err + } + + svc.Nats.Publish("maptest.mapfixes.uploadfix", []byte(j)) + + return nil +} + +// ActionMapfixValidate invokes actionMapfixValidate operation. +// +// Role MapfixRelease changes status from Uploading -> Validated. +// +// POST /mapfixes/{MapfixID}/status/reset-uploading +func (svc *Service) ActionMapfixValidated(ctx context.Context, params api.ActionMapfixValidatedParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixUpload() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapUpload + } + + // check when mapfix was updated + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + if time.Now().Before(mapfix.UpdatedAt.Add(time.Second*10)) { + // the last time the mapfix was updated must be longer than 10 seconds ago + return ErrDelayReset + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusValidated) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) +} + +// ActionMapfixTriggerValidate invokes actionMapfixTriggerValidate operation. +// +// Role Reviewer triggers validation and changes status from Submitted -> Validating. +// +// POST /mapfixes/{MapfixID}/status/trigger-validate +func (svc *Service) ActionMapfixTriggerValidate(ctx context.Context, params api.ActionMapfixTriggerValidateParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixReview() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + // read mapfix (this could be done with a transaction WHERE clause) + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + + has_role, err = userInfo.IsSubmitter(uint64(mapfix.Submitter)) + if err != nil { + return err + } + // check if caller is NOT the submitter + if has_role { + return ErrAcceptOwnMapfix + } + + // Check if an active mapfix with the same target asset id exists + if mapfix.TargetAssetID != 0 { + filter := datastore.Optional() + filter.Add("target_asset_id", mapfix.TargetAssetID) + filter.Add("status_id", ActiveAcceptedMapfixStatuses) + active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{ + Number: 1, + Size: 1, + },datastore.ListSortDisabled) + if err != nil { + return err + } + if len(active_mapfixes) != 0{ + return ErrActiveMapfixSameTargetAssetID + } + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusValidating) + mapfix, err = svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitted}, smap) + if err != nil { + return err + } + + validate_request := model.ValidateMapfixRequest{ + MapfixID: mapfix.ID, + ModelID: mapfix.AssetID, + ModelVersion: mapfix.AssetVersion, + ValidatedModelID: nil, + } + + // sentinel values because we're not using rust + if mapfix.ValidatedAssetID != 0 { + validate_request.ValidatedModelID = &mapfix.ValidatedAssetID + } + + j, err := json.Marshal(validate_request) + if err != nil { + return err + } + + svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) + + return nil +} + +// ActionMapfixRetryValidate invokes actionMapfixRetryValidate operation. +// +// Role Reviewer re-runs validation and changes status from Accepted -> Validating. +// +// POST /mapfixes/{MapfixID}/status/retry-validate +func (svc *Service) ActionMapfixRetryValidate(ctx context.Context, params api.ActionMapfixRetryValidateParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixReview() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusValidating) + mapfix, err := svc.DB.Mapfixes().IfStatusThenUpdateAndGet(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusAccepted}, smap) + if err != nil { + return err + } + + validate_request := model.ValidateMapfixRequest{ + MapfixID: mapfix.ID, + ModelID: mapfix.AssetID, + ModelVersion: mapfix.AssetVersion, + ValidatedModelID: nil, + } + + // sentinel values because we're not using rust + if mapfix.ValidatedAssetID != 0 { + validate_request.ValidatedModelID = &mapfix.ValidatedAssetID + } + + j, err := json.Marshal(validate_request) + if err != nil { + return err + } + + svc.Nats.Publish("maptest.mapfixes.validate", []byte(j)) + + return nil +} + +// ActionMapfixAccepted implements actionMapfixAccepted operation. +// +// Role MapfixReview changes status from Validating -> Accepted. +// +// POST /mapfixes/{MapfixID}/status/reset-validating +func (svc *Service) ActionMapfixAccepted(ctx context.Context, params api.ActionMapfixAcceptedParams) error { + userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) + if !ok { + return ErrUserInfo + } + + has_role, err := userInfo.HasRoleMapfixReview() + if err != nil { + return err + } + // check if caller has required role + if !has_role { + return ErrPermissionDeniedNeedRoleMapReview + } + + // check when mapfix was updated + mapfix, err := svc.DB.Mapfixes().Get(ctx, params.MapfixID) + if err != nil { + return err + } + if time.Now().Before(mapfix.UpdatedAt.Add(time.Second*10)) { + // the last time the mapfix was updated must be longer than 10 seconds ago + return ErrDelayReset + } + + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusAccepted) + smap.Add("status_message", "Manually forced reset") + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) +} diff --git a/pkg/service_internal/mapfixes.go b/pkg/service_internal/mapfixes.go new file mode 100644 index 0000000..cfdf31a --- /dev/null +++ b/pkg/service_internal/mapfixes.go @@ -0,0 +1,61 @@ +package service_internal + +import ( + "context" + + internal "git.itzana.me/strafesnet/maps-service/pkg/internal" + "git.itzana.me/strafesnet/maps-service/pkg/datastore" + "git.itzana.me/strafesnet/maps-service/pkg/model" +) + +// UpdateMapfixValidatedModel implements patchMapfixModel operation. +// +// Update model following role restrictions. +// +// POST /mapfixes/{MapfixID}/validated-model +func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params internal.UpdateMapfixValidatedModelParams) error { + // check if Status is ChangesRequested|Submitted|UnderConstruction + pmap := datastore.Optional() + pmap.AddNotNil("validated_asset_id", params.ValidatedModelID) + pmap.AddNotNil("validated_asset_version", params.ValidatedModelVersion) + // DO NOT reset completed when validated model is updated + // pmap.Add("completed", false) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, pmap) +} + +// ActionMapfixValidate invokes actionMapfixValidate operation. +// +// Role Validator changes status from Validating -> Validated. +// +// POST /mapfixes/{MapfixID}/status/validator-validated +func (svc *Service) ActionMapfixValidated(ctx context.Context, params internal.ActionMapfixValidatedParams) error { + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusValidated) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) +} + +// ActionMapfixAccepted implements actionMapfixAccepted operation. +// +// (Internal endpoint) Role Validator changes status from Validating -> Accepted. +// +// POST /mapfixes/{MapfixID}/status/validator-failed +func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.ActionMapfixAcceptedParams) error { + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusAccepted) + smap.Add("status_message", params.StatusMessage) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap) +} + +// ActionMapfixUploaded implements actionMapfixUploaded operation. +// +// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded. +// +// POST /mapfixes/{MapfixID}/status/validator-uploaded +func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.ActionMapfixUploadedParams) error { + // transaction + smap := datastore.Optional() + smap.Add("status_id", model.MapfixStatusUploaded) + return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap) +}