package web_api import ( "context" "errors" "fmt" "io" "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" "git.itzana.me/strafesnet/maps-service/pkg/service" ) var( CreationPhaseMapfixesLimit = 20 CreationPhaseMapfixStatuses = []model.MapfixStatus{ model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction, } // limit mapfixes in the pipeline to one per target map ActiveAcceptedMapfixStatuses = []model.MapfixStatus{ model.MapfixStatusReleasing, model.MapfixStatusUploaded, model.MapfixStatusUploading, model.MapfixStatusValidated, model.MapfixStatusValidating, model.MapfixStatusAcceptedUnvalidated, } // Allow 5 mapfixes every 10 minutes CreateMapfixRateLimit int64 = 5 CreateMapfixRecencyWindow = time.Second*600 ) var ( ErrCreationPhaseMapfixesLimit = errors.New("Active mapfixes limited to 20") ErrActiveMapfixSameTargetAssetID = errors.New("There is an active mapfix for this map already") ErrAcceptOwnMapfix = fmt.Errorf("%w: You cannot accept your own mapfix as the submitter", ErrPermissionDenied) ErrCreateMapfixRateLimit = errors.New("You must not create more than 5 mapfixes every 10 minutes") ) // POST /mapfixes func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixTriggerCreate) (*api.OperationID, error) { // sanitization if request.AssetID<0 || request.TargetAssetID<0{ return nil, ErrNegativeID } var ModelID=uint64(request.AssetID); var TargetAssetID=uint64(request.TargetAssetID); 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 := service.NewMapfixFilter() filter.SetSubmitter(userId) filter.SetStatuses(CreationPhaseMapfixStatuses) creation_mapfixes, err := svc.inner.ListMapfixes(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 a mapfix targetting the same map exists in creation phase { filter := service.NewMapfixFilter() filter.SetSubmitter(userId) filter.SetTargetAssetID(uint64(request.TargetAssetID)) filter.SetStatuses(CreationPhaseMapfixStatuses) active_mapfixes, err := svc.inner.ListMapfixes(ctx, filter, model.Page{ Number: 1, Size: 1, },datastore.ListSortDisabled) if err != nil { return nil, err } if len(active_mapfixes) != 0{ return nil, ErrActiveMapfixSameTargetAssetID } } // Check if TargetAssetID actually exists { _, err := svc.inner.GetMap(ctx, request.TargetAssetID) if err != nil { // TODO: match specific does not exist grpc error return nil, err } } // Check if too many operations have been created recently { count, err := svc.inner.CountOperationsSince(ctx, int64(userId), time.Now().Add(-CreateMapfixRecencyWindow), ) if err != nil { return nil, err } if CreateMapfixRateLimit < count { return nil, ErrCreateMapfixRateLimit } } operation, err := svc.inner.CreateOperation(ctx, model.Operation{ Owner: userId, StatusID: model.OperationStatusCreated, }) if err != nil { return nil, err } err = svc.inner.NatsCreateMapfix( operation.ID, ModelID, TargetAssetID, request.Description, ) if err != nil { return nil, err } return &api.OperationID{ OperationID: operation.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.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return nil, err } return &api.Mapfix{ ID: mapfix.ID, DisplayName: mapfix.DisplayName, Creator: mapfix.Creator, GameID: int32(mapfix.GameID), 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), Description: mapfix.Description, }, nil } // ListMapfixes implements listMapfixes operation. // // Get list of mapfixes. // // GET /mapfixes func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesParams) (*api.Mapfixes, error) { filter := service.NewMapfixFilter() if display_name, display_name_ok := params.DisplayName.Get(); display_name_ok{ filter.SetDisplayName(display_name) } if creator, creator_ok := params.Creator.Get(); creator_ok{ filter.SetCreator(creator) } if game_id, game_id_ok := params.GameID.Get(); game_id_ok{ filter.SetGameID(uint32(game_id)) } if submitter, submitter_ok := params.Submitter.Get(); submitter_ok{ filter.SetSubmitter(uint64(submitter)) } if asset_id, asset_id_ok := params.AssetID.Get(); asset_id_ok{ filter.SetAssetID(uint64(asset_id)) } if asset_version, asset_version_ok := params.AssetVersion.Get(); asset_version_ok{ filter.SetAssetVersion(uint64(asset_version)) } if target_asset_id, target_asset_id_ok := params.TargetAssetID.Get(); target_asset_id_ok{ filter.SetTargetAssetID(uint64(target_asset_id)) } // TODO: make this a list of statuses if status_id, status_id_ok := params.StatusID.Get(); status_id_ok{ filter.SetStatuses([]model.MapfixStatus{model.MapfixStatus(status_id)}) } sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled))) total, items, err := svc.inner.ListMapfixesWithTotal(ctx, filter, model.Page{ Number: params.Page, Size: params.Limit, },sort) if err != nil { return nil, err } var resp api.Mapfixes resp.Total=total for _, item := range items { resp.Mapfixes = append(resp.Mapfixes, api.Mapfix{ ID: item.ID, DisplayName: item.DisplayName, Creator: item.Creator, GameID: int32(item.GameID), 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), Description: item.Description, }) } 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 } update := service.NewMapfixUpdate() update.SetCompleted(true) return svc.inner.UpdateMapfix(ctx, params.MapfixID, update) } // 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.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } userId, err := userInfo.GetUserID() if err != nil { return err } // check if caller is the submitter has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } OldModelID := mapfix.AssetID OldModelVersion := mapfix.AssetVersion NewModelID := uint64(params.ModelID) NewModelVersion := uint64(params.ModelVersion) // check if Status is ChangesRequested|Submitted|UnderConstruction update := service.NewMapfixUpdate() update.SetAssetID(NewModelID) update.SetAssetVersion(NewModelVersion) //always reset completed when model changes update.SetCompleted(false) allow_statuses := []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusSubmitted, model.MapfixStatusUnderConstruction} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataChangeModel{ OldModelID: OldModelID, OldModelVersion: OldModelVersion, NewModelID: NewModelID, NewModelVersion: NewModelVersion, } return svc.inner.CreateAuditEventChangeModel( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // UpdateMapfixDescription implements updateMapfixDescription operation. // // Update description (submitter only, status ChangesRequested or UnderConstruction). // // PATCH /mapfixes/{MapfixID}/description func (svc *Service) UpdateMapfixDescription(ctx context.Context, req api.UpdateMapfixDescriptionReq, params api.UpdateMapfixDescriptionParams) error { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo } // read mapfix mapfix, err := svc.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } userId, err := userInfo.GetUserID() if err != nil { return err } // check if caller is the submitter if userId != mapfix.Submitter { return ErrPermissionDeniedNotSubmitter } // read the new description from request body data, err := io.ReadAll(req) if err != nil { return err } newDescription := string(data) // check if Status is ChangesRequested or UnderConstruction update := service.NewMapfixUpdate() update.SetDescription(newDescription) allow_statuses := []model.MapfixStatus{model.MapfixStatusChangesRequested, model.MapfixStatusUnderConstruction} return svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) } // 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 ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() if err != nil { return err } // transaction target_status := model.MapfixStatusRejected update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitted} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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 ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() if err != nil { return err } // transaction target_status := model.MapfixStatusChangesRequested update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{ model.MapfixStatusUploaded, model.MapfixStatusValidated, model.MapfixStatusAcceptedUnvalidated, model.MapfixStatusSubmitted, } err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } userId, err := userInfo.GetUserID() if err != nil { return err } // check if caller is the submitter has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // transaction target_status := model.MapfixStatusUnderConstruction update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitted, model.MapfixStatusChangesRequested} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // ActionMapfixTriggerSubmit invokes actionMapfixTriggerSubmit operation. // // Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting. // // POST /mapfixes/{MapfixID}/status/trigger-submit func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.ActionMapfixTriggerSubmitParams) error { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo } userId, err := userInfo.GetUserID() if err != nil { return err } // read mapfix (this could be done with a transaction WHERE clause) mapfix, err := svc.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } // check if caller is the submitter has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // read map to get current DisplayName and such target_map, err := svc.inner.GetMap(ctx, int64(mapfix.TargetAssetID)) if err != nil { return err } // transaction target_status := model.MapfixStatusSubmitting update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } err = svc.inner.NatsCheckMapfix( mapfix.ID, mapfix.AssetID, false, target_map.DisplayName, target_map.Creator, target_map.GameID, ) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // ActionMapfixTriggerSubmitUnchecked invokes actionMapfixTriggerSubmitUnchecked operation. // // Role Reviewer changes status from ChangesRequested -> Submitting. // // POST /mapfixes/{MapfixID}/status/trigger-submit-unchecked func (svc *Service) ActionMapfixTriggerSubmitUnchecked(ctx context.Context, params api.ActionMapfixTriggerSubmitUncheckedParams) error { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo } userId, err := userInfo.GetUserID() if err != nil { return err } // read mapfix (this could be done with a transaction WHERE clause) mapfix, err := svc.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } // check if caller is the submitter is_submitter := userId == mapfix.Submitter if is_submitter { return ErrAcceptOwnMapfix } has_mapfix_review, err := userInfo.HasRoleMapfixReview() if err != nil { return err } if !has_mapfix_review { return ErrPermissionDeniedNeedRoleMapfixReview } // read map to get current DisplayName and such target_map, err := svc.inner.GetMap(ctx, int64(mapfix.TargetAssetID)) if err != nil { return err } // transaction target_status := model.MapfixStatusSubmitting update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusChangesRequested} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } err = svc.inner.NatsCheckMapfix( mapfix.ID, mapfix.AssetID, true, target_map.DisplayName, target_map.Creator, target_map.GameID, ) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // ActionMapfixResetSubmitting implements actionMapfixResetSubmitting operation. // // Role MapfixReview changes status from Submitting -> UnderConstruction. // // POST /mapfixes/{MapfixID}/status/reset-submitting func (svc *Service) ActionMapfixResetSubmitting(ctx context.Context, params api.ActionMapfixResetSubmittingParams) error { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo } userId, err := userInfo.GetUserID() if err != nil { return err } // check when mapfix was updated mapfix, err := svc.inner.GetMapfix(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 } // check if caller has required role has_role := userId == mapfix.Submitter if !has_role { return ErrPermissionDeniedNotSubmitter } // transaction target_status := model.MapfixStatusUnderConstruction update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitting} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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 ErrPermissionDeniedNeedRoleMapfixUpload } userId, err := userInfo.GetUserID() if err != nil { return err } // transaction target_status := model.MapfixStatusUploading update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusValidated} mapfix, err := svc.inner.UpdateAndGetMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } // this is a map fix err = svc.inner.NatsUploadMapfix( mapfix.ID, mapfix.ValidatedAssetID, mapfix.ValidatedAssetVersion, mapfix.TargetAssetID, ) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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 ErrPermissionDeniedNeedRoleMapfixUpload } userId, err := userInfo.GetUserID() if err != nil { return err } // check when mapfix was updated mapfix, err := svc.inner.GetMapfix(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 target_status := model.MapfixStatusValidated update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusUploading} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // ActionMapfixTriggerRelease invokes actionMapfixTriggerRelease operation. // // Role MapfixUpload changes status from Uploaded -> Releasing. // // POST /mapfixes/{MapfixID}/status/trigger-release func (svc *Service) ActionMapfixTriggerRelease(ctx context.Context, params api.ActionMapfixTriggerReleaseParams) 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 ErrPermissionDeniedNeedRoleMapfixUpload } userId, err := userInfo.GetUserID() if err != nil { return err } // transaction target_status := model.MapfixStatusReleasing update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusUploaded} mapfix, err := svc.inner.UpdateAndGetMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } // this is a map fix err = svc.inner.NatsReleaseMapfix( mapfix.ID, mapfix.ValidatedAssetID, mapfix.ValidatedAssetVersion, mapfix.TargetAssetID, ) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // ActionMapfixUploaded invokes actionMapfixUploaded operation. // // Role MapfixUpload manually resets releasing softlock and changes status from Releasing -> Uploaded. // // POST /mapfixes/{MapfixID}/status/reset-releasing func (svc *Service) ActionMapfixUploaded(ctx context.Context, params api.ActionMapfixUploadedParams) 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 ErrPermissionDeniedNeedRoleMapfixUpload } userId, err := userInfo.GetUserID() if err != nil { return err } // check when mapfix was updated mapfix, err := svc.inner.GetMapfix(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 target_status := model.MapfixStatusUploaded update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusReleasing} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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 ErrPermissionDeniedNeedRoleMapfixReview } // read mapfix (this could be done with a transaction WHERE clause) mapfix, err := svc.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } userId, err := userInfo.GetUserID() if err != nil { return err } // check if caller is NOT the submitter has_role = userId == mapfix.Submitter if has_role { return ErrAcceptOwnMapfix } // Check if an active mapfix with the same target asset id exists if mapfix.TargetAssetID != 0 { filter := service.NewMapfixFilter() filter.SetTargetAssetID(mapfix.TargetAssetID) filter.SetStatuses(ActiveAcceptedMapfixStatuses) active_mapfixes, err := svc.inner.ListMapfixes(ctx, filter, model.Page{ Number: 1, Size: 1, },datastore.ListSortDisabled) if err != nil { return err } if len(active_mapfixes) != 0{ return ErrActiveMapfixSameTargetAssetID } } // transaction target_status := model.MapfixStatusValidating update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitted} mapfix, err = svc.inner.UpdateAndGetMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } err = svc.inner.NatsValidateMapfix( mapfix.ID, mapfix.AssetID, mapfix.AssetVersion, mapfix.ValidatedAssetID, ) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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 ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() if err != nil { return err } // transaction target_status := model.MapfixStatusValidating update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusAcceptedUnvalidated} mapfix, err := svc.inner.UpdateAndGetMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } err = svc.inner.NatsValidateMapfix( mapfix.ID, mapfix.AssetID, mapfix.AssetVersion, mapfix.ValidatedAssetID, ) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // 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 ErrPermissionDeniedNeedRoleMapfixReview } userId, err := userInfo.GetUserID() if err != nil { return err } // check when mapfix was updated mapfix, err := svc.inner.GetMapfix(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 target_status := model.MapfixStatusAcceptedUnvalidated update := service.NewMapfixUpdate() update.SetStatusID(target_status) allow_statuses := []model.MapfixStatus{model.MapfixStatusValidating} err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update) if err != nil { return err } event_data := model.AuditEventDataAction{ TargetStatus: uint32(target_status), } return svc.inner.CreateAuditEventAction( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // CreateMapfixAuditComment implements createMapfixAuditComment operation. // // Post a comment to the audit log // // POST /mapfixes/{MapfixID}/comment func (svc *Service) CreateMapfixAuditComment(ctx context.Context, req api.CreateMapfixAuditCommentReq, params api.CreateMapfixAuditCommentParams) (error) { userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle) if !ok { return ErrUserInfo } has_role, err := userInfo.HasRoleMapfixReview() if err != nil { return err } userId, err := userInfo.GetUserID() if err != nil { return err } if !has_role { // Submitter has special permission to comment on their mapfix mapfix, err := svc.inner.GetMapfix(ctx, params.MapfixID) if err != nil { return err } if mapfix.Submitter != userId { return ErrPermissionDeniedNeedRoleMapfixReview } } data, err := io.ReadAll(req) if err != nil { return err } event_data := model.AuditEventDataComment{ Comment: string(data), } return svc.inner.CreateAuditEventComment( ctx, userId, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, event_data, ) } // ListMapfixAuditEvents invokes listMapfixAuditEvents operation. // // Retrieve a list of audit events. // // GET /mapfixes/{MapfixID}/audit-events func (svc *Service) ListMapfixAuditEvents(ctx context.Context, params api.ListMapfixAuditEventsParams) ([]api.AuditEvent, error) { return svc.inner.ListAuditEvents( ctx, model.Resource{ ID: params.MapfixID, Type: model.ResourceMapfix, }, model.Page{ Number: params.Page, Size: params.Limit, }, ) }