1194 lines
31 KiB
Go
1194 lines
31 KiB
Go
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(
|
|
CreationPhaseSubmissionsLimit = 20
|
|
CreationPhaseSubmissionStatuses = []model.SubmissionStatus{
|
|
model.SubmissionStatusChangesRequested,
|
|
model.SubmissionStatusSubmitted,
|
|
model.SubmissionStatusUnderConstruction,
|
|
}
|
|
// Allow 5 submissions every 10 minutes
|
|
CreateSubmissionRateLimit int64 = 5
|
|
CreateSubmissionRecencyWindow = time.Second*600
|
|
)
|
|
|
|
var (
|
|
ErrCreationPhaseSubmissionsLimit = errors.New("Active submissions limited to 20")
|
|
ErrUploadedAssetIDAlreadyExists = errors.New("The submission UploadedAssetID is already set")
|
|
ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released")
|
|
ErrReleaseNoUploadedAssetID = errors.New("Only submissions with a UploadedAssetID can be released")
|
|
ErrAcceptOwnSubmission = fmt.Errorf("%w: You cannot accept your own submission as the submitter", ErrPermissionDenied)
|
|
ErrCreateSubmissionRateLimit = errors.New("You must not create more than 5 submissions every 10 minutes")
|
|
)
|
|
|
|
// POST /submissions
|
|
func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) {
|
|
// sanitization
|
|
if request.AssetID<0{
|
|
return nil, ErrNegativeID
|
|
}
|
|
var ModelID=uint64(request.AssetID);
|
|
|
|
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 submissions in the creation phase exceeds the limit
|
|
{
|
|
filter := service.NewSubmissionFilter()
|
|
filter.SetSubmitter(userId)
|
|
filter.SetStatuses(CreationPhaseSubmissionStatuses)
|
|
creation_submissions, err := svc.inner.ListSubmissions(ctx, filter, model.Page{
|
|
Number: 1,
|
|
Size: int32(CreationPhaseSubmissionsLimit),
|
|
},datastore.ListSortDisabled)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if CreationPhaseSubmissionsLimit <= len(creation_submissions) {
|
|
return nil, ErrCreationPhaseSubmissionsLimit
|
|
}
|
|
}
|
|
|
|
// Check if too many operations have been created recently
|
|
{
|
|
count, err := svc.inner.CountOperationsSince(ctx,
|
|
int64(userId),
|
|
time.Now().Add(-CreateSubmissionRecencyWindow),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if CreateSubmissionRateLimit < count {
|
|
return nil, ErrCreateSubmissionRateLimit
|
|
}
|
|
}
|
|
|
|
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
|
|
Owner: userId,
|
|
StatusID: model.OperationStatusCreated,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = svc.inner.NatsCreateSubmission(
|
|
operation.ID,
|
|
ModelID,
|
|
request.DisplayName,
|
|
request.Creator,
|
|
uint32(request.GameID),
|
|
uint32(model.SubmissionStatusUnderConstruction),
|
|
uint32(model.RolesEmpty),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &api.OperationID{
|
|
OperationID: operation.ID,
|
|
}, nil
|
|
}
|
|
// POST /submissions-admin
|
|
func (svc *Service) CreateSubmissionAdmin(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) {
|
|
// sanitization
|
|
if request.AssetID<0{
|
|
return nil, ErrNegativeID
|
|
}
|
|
var ModelID=uint64(request.AssetID);
|
|
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return nil, ErrUserInfo
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
roles, err := userInfo.GetRoles()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check if caller has required role
|
|
has_role := roles & model.RolesSubmissionReview == model.RolesSubmissionReview
|
|
if !has_role {
|
|
return nil, ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
// Check if too many operations have been created recently
|
|
{
|
|
count, err := svc.inner.CountOperationsSince(ctx,
|
|
int64(userId),
|
|
time.Now().Add(-CreateSubmissionRecencyWindow),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if CreateSubmissionRateLimit < count {
|
|
return nil, ErrCreateSubmissionRateLimit
|
|
}
|
|
}
|
|
|
|
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
|
|
Owner: userId,
|
|
StatusID: model.OperationStatusCreated,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = svc.inner.NatsCreateSubmission(
|
|
operation.ID,
|
|
ModelID,
|
|
request.DisplayName,
|
|
request.Creator,
|
|
uint32(request.GameID),
|
|
uint32(model.SubmissionStatusChangesRequested),
|
|
uint32(roles),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &api.OperationID{
|
|
OperationID: operation.ID,
|
|
}, nil
|
|
}
|
|
|
|
// GetSubmission implements getSubmission operation.
|
|
//
|
|
// Retrieve map with ID.
|
|
//
|
|
// GET /submissions/{SubmissionID}
|
|
func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionParams) (*api.Submission, error) {
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &api.Submission{
|
|
ID: submission.ID,
|
|
DisplayName: submission.DisplayName,
|
|
Creator: submission.Creator,
|
|
GameID: int32(submission.GameID),
|
|
CreatedAt: submission.CreatedAt.Unix(),
|
|
UpdatedAt: submission.UpdatedAt.Unix(),
|
|
Submitter: int64(submission.Submitter),
|
|
AssetID: int64(submission.AssetID),
|
|
AssetVersion: int64(submission.AssetVersion),
|
|
Completed: submission.Completed,
|
|
UploadedAssetID: api.NewOptInt64(int64(submission.UploadedAssetID)),
|
|
StatusID: int32(submission.StatusID),
|
|
}, nil
|
|
}
|
|
|
|
// ListSubmissions implements listSubmissions operation.
|
|
//
|
|
// Get list of submissions.
|
|
//
|
|
// GET /submissions
|
|
func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissionsParams) (*api.Submissions, error) {
|
|
filter := service.NewSubmissionFilter()
|
|
|
|
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 uploaded_asset_id, uploaded_asset_id_ok := params.UploadedAssetID.Get(); uploaded_asset_id_ok{
|
|
filter.SetUploadedAssetID(uint64(uploaded_asset_id))
|
|
}
|
|
if status_id, status_id_ok := params.StatusID.Get(); status_id_ok{
|
|
filter.SetStatuses([]model.SubmissionStatus{model.SubmissionStatus(status_id)})
|
|
}
|
|
|
|
sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled)))
|
|
|
|
total, items, err := svc.inner.ListSubmissionsWithTotal(ctx, filter, model.Page{
|
|
Number: params.Page,
|
|
Size: params.Limit,
|
|
},sort)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var resp api.Submissions
|
|
resp.Total=total
|
|
for _, item := range items {
|
|
resp.Submissions = append(resp.Submissions, api.Submission{
|
|
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,
|
|
UploadedAssetID: api.NewOptInt64(int64(item.UploadedAssetID)),
|
|
StatusID: int32(item.StatusID),
|
|
})
|
|
}
|
|
|
|
return &resp, nil
|
|
}
|
|
|
|
// PatchSubmissionCompleted implements patchSubmissionCompleted operation.
|
|
//
|
|
// Retrieve map with ID.
|
|
//
|
|
// POST /submissions/{SubmissionID}/completed
|
|
func (svc *Service) SetSubmissionCompleted(ctx context.Context, params api.SetSubmissionCompletedParams) 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.NewSubmissionUpdate()
|
|
update.SetCompleted(true)
|
|
return svc.inner.UpdateSubmission(ctx, params.SubmissionID, update)
|
|
}
|
|
|
|
// UpdateSubmissionModel implements patchSubmissionModel operation.
|
|
//
|
|
// Update model following role restrictions.
|
|
//
|
|
// POST /submissions/{SubmissionID}/model
|
|
func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.UpdateSubmissionModelParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
// read submission (this could be done with a transaction WHERE clause)
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if caller is the submitter
|
|
has_role := userId == submission.Submitter
|
|
if !has_role {
|
|
return ErrPermissionDeniedNotSubmitter
|
|
}
|
|
|
|
OldModelID := submission.AssetID
|
|
OldModelVersion := submission.AssetVersion
|
|
NewModelID := uint64(params.ModelID)
|
|
NewModelVersion := uint64(params.ModelVersion)
|
|
|
|
// check if Status is ChangesRequested|Submitted|UnderConstruction
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetAssetID(NewModelID)
|
|
update.SetAssetVersion(NewModelVersion)
|
|
//always reset completed when model changes
|
|
update.SetCompleted(false)
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusUnderConstruction}, 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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionReject invokes actionSubmissionReject operation.
|
|
//
|
|
// Role Reviewer changes status from Submitted -> Rejected.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/reject
|
|
func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusRejected
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusSubmitted}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
|
|
//
|
|
// Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/request-changes
|
|
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusChangesRequested
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAcceptedUnvalidated, model.SubmissionStatusSubmitted}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
|
|
//
|
|
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/revoke
|
|
func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
// read submission (this could be done with a transaction WHERE clause)
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if caller is the submitter
|
|
has_role := userId == submission.Submitter
|
|
if !has_role {
|
|
return ErrPermissionDeniedNotSubmitter
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusUnderConstruction
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionTriggerSubmit invokes actionSubmissionTriggerSubmit operation.
|
|
//
|
|
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitting.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/trigger-submit
|
|
func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params api.ActionSubmissionTriggerSubmitParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
// read submission (this could be done with a transaction WHERE clause)
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if caller is the submitter
|
|
is_submitter := userId == submission.Submitter
|
|
// neither = deny
|
|
if !is_submitter {
|
|
has_submission_review, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !has_submission_review {
|
|
return ErrPermissionDeniedNotSubmitter
|
|
}
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusSubmitting
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = svc.inner.NatsCheckSubmission(
|
|
submission.ID,
|
|
submission.AssetID,
|
|
false,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
event_data := model.AuditEventDataAction{
|
|
TargetStatus: uint32(target_status),
|
|
}
|
|
|
|
return svc.inner.CreateAuditEventAction(
|
|
ctx,
|
|
userId,
|
|
model.Resource{
|
|
ID: params.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionTriggerSubmitUnchecked invokes actionSubmissionTriggerSubmitUnchecked operation.
|
|
//
|
|
// Role Reviewer changes status from ChangesRequested -> Submitting.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/trigger-submit-unchecked
|
|
func (svc *Service) ActionSubmissionTriggerSubmitUnchecked(ctx context.Context, params api.ActionSubmissionTriggerSubmitUncheckedParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
// read submission (this could be done with a transaction WHERE clause)
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if caller is the submitter
|
|
is_submitter := userId == submission.Submitter
|
|
if is_submitter {
|
|
return ErrAcceptOwnSubmission
|
|
}
|
|
|
|
has_submission_review, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !has_submission_review {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusSubmitting
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusChangesRequested}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = svc.inner.NatsCheckSubmission(
|
|
submission.ID,
|
|
submission.AssetID,
|
|
true,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
event_data := model.AuditEventDataAction{
|
|
TargetStatus: uint32(target_status),
|
|
}
|
|
|
|
return svc.inner.CreateAuditEventAction(
|
|
ctx,
|
|
userId,
|
|
model.Resource{
|
|
ID: params.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionResetSubmitting implements actionSubmissionResetSubmitting operation.
|
|
//
|
|
// Role SubmissionReview changes status from Submitting -> UnderConstruction.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/reset-submitting
|
|
func (svc *Service) ActionSubmissionResetSubmitting(ctx context.Context, params api.ActionSubmissionResetSubmittingParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check when submission was updated
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
|
|
// the last time the submission was updated must be longer than 10 seconds ago
|
|
return ErrDelayReset
|
|
}
|
|
|
|
// check if caller has required role
|
|
has_role := userId == submission.Submitter
|
|
if !has_role {
|
|
return ErrPermissionDeniedNotSubmitter
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusUnderConstruction
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusSubmitting}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
|
|
//
|
|
// Role Admin changes status from Validated -> Uploading.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/trigger-upload
|
|
func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params api.ActionSubmissionTriggerUploadParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionUpload()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionUpload
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusUploading
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusValidated}
|
|
submission, err := svc.inner.UpdateAndGetSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// sentinel value because we are not using rust
|
|
if submission.UploadedAssetID == 0 {
|
|
// this is a new map
|
|
err = svc.inner.NatsUploadSubmission(
|
|
submission.ID,
|
|
submission.ValidatedAssetID,
|
|
submission.ValidatedAssetVersion,
|
|
// upload ModelName as displayname, whatever
|
|
submission.DisplayName,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// refuse to operate
|
|
return ErrUploadedAssetIDAlreadyExists
|
|
}
|
|
|
|
event_data := model.AuditEventDataAction{
|
|
TargetStatus: uint32(target_status),
|
|
}
|
|
|
|
return svc.inner.CreateAuditEventAction(
|
|
ctx,
|
|
userId,
|
|
model.Resource{
|
|
ID: params.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
|
|
//
|
|
// Role SubmissionRelease changes status from Uploading -> Validated.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/reset-uploading
|
|
func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.ActionSubmissionValidatedParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionUpload()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionUpload
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check when submission was updated
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
|
|
// the last time the submission was updated must be longer than 10 seconds ago
|
|
return ErrDelayReset
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusValidated
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusUploading}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
|
|
//
|
|
// Role Reviewer triggers validation and changes status from Submitted -> Validating.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/trigger-validate
|
|
func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
// read submission (this could be done with a transaction WHERE clause)
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if caller is NOT the submitter
|
|
has_role = userId == submission.Submitter
|
|
if has_role {
|
|
return ErrAcceptOwnSubmission
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusValidating
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusSubmitted}
|
|
submission, err = svc.inner.UpdateAndGetSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = svc.inner.NatsValidateSubmission(
|
|
submission.ID,
|
|
submission.AssetID,
|
|
submission.AssetVersion,
|
|
submission.ValidatedAssetID,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
event_data := model.AuditEventDataAction{
|
|
TargetStatus: uint32(target_status),
|
|
}
|
|
|
|
return svc.inner.CreateAuditEventAction(
|
|
ctx,
|
|
userId,
|
|
model.Resource{
|
|
ID: params.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionRetryValidate invokes actionSubmissionRetryValidate operation.
|
|
//
|
|
// Role Reviewer re-runs validation and changes status from Accepted -> Validating.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/retry-validate
|
|
func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params api.ActionSubmissionRetryValidateParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusValidating
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusAcceptedUnvalidated}
|
|
submission, err := svc.inner.UpdateAndGetSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = svc.inner.NatsValidateSubmission(
|
|
submission.ID,
|
|
submission.AssetID,
|
|
submission.AssetVersion,
|
|
submission.ValidatedAssetID,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
event_data := model.AuditEventDataAction{
|
|
TargetStatus: uint32(target_status),
|
|
}
|
|
|
|
return svc.inner.CreateAuditEventAction(
|
|
ctx,
|
|
userId,
|
|
model.Resource{
|
|
ID: params.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
|
|
//
|
|
// Role SubmissionReview changes status from Validating -> Accepted.
|
|
//
|
|
// POST /submissions/{SubmissionID}/status/reset-validating
|
|
func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.ActionSubmissionAcceptedParams) error {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionReview()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check when submission was updated
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
|
|
// the last time the submission was updated must be longer than 10 seconds ago
|
|
return ErrDelayReset
|
|
}
|
|
|
|
// transaction
|
|
target_status := model.SubmissionStatusAcceptedUnvalidated
|
|
update := service.NewSubmissionUpdate()
|
|
update.SetStatusID(target_status)
|
|
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusValidating}
|
|
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ReleaseSubmissions invokes releaseSubmissions operation.
|
|
//
|
|
// Release a set of uploaded maps.
|
|
//
|
|
// POST /release-submissions
|
|
func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.ReleaseInfo) (*api.OperationID, error) {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return nil, ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionRelease()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// check if caller has required role
|
|
if !has_role {
|
|
return nil, ErrPermissionDeniedNeedRoleSubmissionRelease
|
|
}
|
|
|
|
userId, err := userInfo.GetUserID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
idList := make([]int64, len(request))
|
|
for i, releaseInfo := range request {
|
|
idList[i] = releaseInfo.SubmissionID
|
|
}
|
|
|
|
// fetch submissions
|
|
submissions, err := svc.inner.GetSubmissionList(ctx, idList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// the submissions are not ordered the same as the idList!
|
|
id_to_submission := make(map[int64]*model.Submission, len(submissions))
|
|
|
|
// check each submission to make sure it is ready to release
|
|
for _,submission := range submissions{
|
|
if submission.StatusID != model.SubmissionStatusUploaded{
|
|
return nil, ErrReleaseInvalidStatus
|
|
}
|
|
if submission.UploadedAssetID == 0{
|
|
return nil, ErrReleaseNoUploadedAssetID
|
|
}
|
|
id_to_submission[submission.ID] = &submission
|
|
}
|
|
|
|
// construct batch release nats message
|
|
release_submissions := make([]model.ReleaseSubmissionRequest, len(request))
|
|
for i, release_info := range request {
|
|
// from request
|
|
release_submissions[i].ReleaseDate = release_info.Date.Unix()
|
|
release_submissions[i].SubmissionID = release_info.SubmissionID
|
|
submission := id_to_submission[release_info.SubmissionID]
|
|
// from submission
|
|
release_submissions[i].ModelID = submission.ValidatedAssetID
|
|
release_submissions[i].ModelVersion = submission.ValidatedAssetVersion
|
|
// for map create
|
|
release_submissions[i].UploadedAssetID = submission.UploadedAssetID
|
|
release_submissions[i].DisplayName = submission.DisplayName
|
|
release_submissions[i].Creator = submission.Creator
|
|
release_submissions[i].GameID = submission.GameID
|
|
release_submissions[i].Submitter = submission.Submitter
|
|
}
|
|
|
|
// create a trackable long-running operation
|
|
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
|
|
Owner: userId,
|
|
StatusID: model.OperationStatusCreated,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// this is a map fix
|
|
err = svc.inner.NatsBatchReleaseSubmissions(
|
|
release_submissions,
|
|
operation.ID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &api.OperationID{
|
|
OperationID: operation.ID,
|
|
}, nil
|
|
}
|
|
|
|
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
|
|
//
|
|
// Post a comment to the audit log
|
|
//
|
|
// POST /submissions/{SubmissionID}/comment
|
|
func (svc *Service) CreateSubmissionAuditComment(ctx context.Context, req api.CreateSubmissionAuditCommentReq, params api.CreateSubmissionAuditCommentParams) (error) {
|
|
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
|
|
if !ok {
|
|
return ErrUserInfo
|
|
}
|
|
|
|
has_role, err := userInfo.HasRoleSubmissionReview()
|
|
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 submission
|
|
submission, err := svc.inner.GetSubmission(ctx, params.SubmissionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if submission.Submitter != userId {
|
|
return ErrPermissionDeniedNeedRoleSubmissionReview
|
|
}
|
|
}
|
|
|
|
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.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
event_data,
|
|
)
|
|
}
|
|
|
|
// ListSubmissionAuditEvents invokes listSubmissionAuditEvents operation.
|
|
//
|
|
// Retrieve a list of audit events.
|
|
//
|
|
// GET /submissions/{SubmissionID}/audit-events
|
|
func (svc *Service) ListSubmissionAuditEvents(ctx context.Context, params api.ListSubmissionAuditEventsParams) ([]api.AuditEvent, error) {
|
|
return svc.inner.ListAuditEvents(
|
|
ctx,
|
|
model.Resource{
|
|
ID: params.SubmissionID,
|
|
Type: model.ResourceSubmission,
|
|
},
|
|
model.Page{
|
|
Number: params.Page,
|
|
Size: params.Limit,
|
|
},
|
|
)
|
|
}
|