maps-service/pkg/service/submissions.go

447 lines
13 KiB
Go
Raw Permalink Normal View History

2024-11-26 18:30:58 -05:00
package service
2024-11-26 18:28:48 -05:00
import (
"context"
2024-11-29 15:52:44 -08:00
"encoding/json"
"errors"
2024-11-29 15:52:44 -08:00
2024-11-26 18:28:48 -05:00
"git.itzana.me/strafesnet/maps-service/pkg/api"
2024-11-27 16:38:22 -08:00
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
2024-11-29 15:52:44 -08:00
"git.itzana.me/strafesnet/maps-service/pkg/model"
2024-11-26 18:28:48 -05:00
)
var(
CreationPhaseSubmissionsLimit = 20
CreationPhaseSubmissionStatuses = []model.Status{
model.StatusChangesRequested,
model.StatusSubmitted,
model.StatusUnderConstruction,
}
ActiveSubmissionStatuses = []model.Status{
model.StatusUploaded,
model.StatusUploading,
model.StatusValidated,
model.StatusValidating,
model.StatusAccepted,
model.StatusChangesRequested,
model.StatusSubmitted,
model.StatusUnderConstruction,
}
)
var (
ErrCreationPhaseSubmissionsLimit = errors.New("Active submissions limited to 20")
ErrActiveSubmissionSameAssetID = errors.New("There is an active submission with the same AssetID")
ErrActiveSubmissionSameTargetAssetID = errors.New("There is an active submission with the same TargetAssetID")
)
2024-11-26 18:28:48 -05:00
// POST /submissions
2024-12-05 18:48:26 -08:00
func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionCreate) (*api.ID, error) {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return nil, ErrUserInfo
}
userId := userInfo.UserID
// Check if user's submissions in the creation phase exceeds the limit
{
filter := datastore.Optional()
filter.Add("submitter", int64(userId))
filter.Add("status_id", CreationPhaseSubmissionStatuses)
creation_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: int32(CreationPhaseSubmissionsLimit),
})
if err != nil {
return nil, err
}
if CreationPhaseSubmissionsLimit <= len(creation_submissions) {
return nil, ErrCreationPhaseSubmissionsLimit
}
}
// Check if an active submission 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", ActiveSubmissionStatuses)
active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: 1,
})
if err != nil {
return nil, err
}
if len(active_submissions) != 0{
return nil, ErrActiveSubmissionSameAssetID
}
}
// Check if an active submission with the same target asset id exists
if request.TargetAssetID.IsSet() && request.TargetAssetID.Value != 0{
filter := datastore.Optional()
filter.Add("target_asset_id", request.TargetAssetID.Value)
filter.Add("status_id", ActiveSubmissionStatuses)
active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: 1,
})
if err != nil {
return nil, err
}
if len(active_submissions) != 0{
return nil, ErrActiveSubmissionSameTargetAssetID
}
}
2024-11-27 15:38:17 -08:00
submission, err := svc.DB.Submissions().Create(ctx, model.Submission{
ID: 0,
2024-12-05 18:48:26 -08:00
DisplayName: request.DisplayName,
Creator: request.Creator,
GameID: request.GameID,
Submitter: int64(userId),
AssetID: request.AssetID,
AssetVersion: request.AssetVersion,
2024-11-27 15:38:17 -08:00
Completed: false,
TargetAssetID: request.TargetAssetID.Value,
StatusID: model.StatusUnderConstruction,
2024-11-27 15:38:17 -08:00
})
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-27 15:38:17 -08:00
return nil, err
}
return &api.ID{
2024-12-12 17:29:20 -05:00
ID: submission.ID,
2024-11-27 15:38:17 -08:00
}, nil
2024-11-26 18:28:48 -05:00
}
// GetSubmission implements getSubmission operation.
//
// Retrieve map with ID.
//
// GET /submissions/{SubmissionID}
2024-11-26 18:30:58 -05:00
func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionParams) (*api.Submission, error) {
2024-11-26 15:55:56 -08:00
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-26 15:55:56 -08:00
return nil, err
}
return &api.Submission{
2024-12-12 17:29:20 -05:00
ID: submission.ID,
DisplayName: submission.DisplayName,
Creator: submission.Creator,
GameID: 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,
TargetAssetID: api.NewOptInt64(int64(submission.TargetAssetID)),
StatusID: int32(submission.StatusID),
2024-11-26 15:55:56 -08:00
}, nil
2024-11-26 18:28:48 -05:00
}
// ListSubmissions implements listSubmissions operation.
//
// Get list of submissions.
//
// GET /submissions
2024-12-17 21:39:04 -08:00
func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissionsParams) ([]api.Submission, error) {
2024-11-27 16:38:22 -08:00
filter := datastore.Optional()
2024-12-17 21:39:04 -08:00
if params.DisplayName.IsSet(){
filter.Add("display_name", params.DisplayName.Value)
}
if params.Creator.IsSet(){
filter.Add("creator", params.Creator.Value)
}
if params.GameID.IsSet(){
filter.Add("game_id", params.GameID.Value)
2024-11-27 16:38:22 -08:00
}
items, err := svc.DB.Submissions().List(ctx, filter, model.Page{
2024-12-17 21:39:04 -08:00
Number: params.Page,
Size: params.Limit,
2024-11-27 16:38:22 -08:00
})
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-27 16:38:22 -08:00
return nil, err
}
var resp []api.Submission
for i := 0; i < len(items); i++ {
resp = append(resp, api.Submission{
2024-12-12 17:29:20 -05:00
ID: items[i].ID,
DisplayName: items[i].DisplayName,
Creator: items[i].Creator,
GameID: items[i].GameID,
CreatedAt: items[i].CreatedAt.Unix(),
UpdatedAt: items[i].UpdatedAt.Unix(),
Submitter: int64(items[i].Submitter),
AssetID: int64(items[i].AssetID),
AssetVersion: int64(items[i].AssetVersion),
Completed: items[i].Completed,
TargetAssetID: api.NewOptInt64(int64(items[i].TargetAssetID)),
StatusID: int32(items[i].StatusID),
2024-11-27 16:38:22 -08:00
})
}
return resp, nil
2024-11-26 18:28:48 -05:00
}
// PatchSubmissionCompleted implements patchSubmissionCompleted operation.
//
// Retrieve map with ID.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/completed
func (svc *Service) SetSubmissionCompleted(ctx context.Context, params api.SetSubmissionCompletedParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
2024-11-27 17:27:40 -08:00
// check if caller has MaptestGame role (request must originate from a maptest roblox game)
if !userInfo.Roles.Maptest {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 16:44:50 -08:00
pmap := datastore.Optional()
2024-11-27 17:22:59 -08:00
pmap.Add("completed", true)
err := svc.DB.Submissions().Update(ctx, params.SubmissionID, pmap)
return err
2024-11-26 18:28:48 -05:00
}
// UpdateSubmissionModel implements patchSubmissionModel operation.
2024-11-26 18:28:48 -05:00
//
// Update model following role restrictions.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/model
func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.UpdateSubmissionModelParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
// read submission (this could be done with a transaction WHERE clause)
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 13:58:47 -08:00
return err
}
// check if caller is the submitter
if !userInfo.IsSubmitter(uint64(submission.Submitter)) {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 17:27:40 -08:00
// check if Status is ChangesRequested|Submitted|UnderConstruction
2024-11-27 16:44:50 -08:00
pmap := datastore.Optional()
pmap.AddNotNil("asset_id", params.ModelID)
pmap.AddNotNil("asset_version", params.VersionID)
//always reset completed when model changes
2024-12-12 17:29:20 -05:00
pmap.Add("completed", false)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusChangesRequested, model.StatusSubmitted, model.StatusUnderConstruction}, pmap)
2024-11-26 18:28:48 -05:00
}
// ActionSubmissionReject invokes actionSubmissionReject operation.
//
// Role Reviewer changes status from Submitted -> Rejected.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/status/reject
func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.ActionSubmissionRejectParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
2024-11-27 18:19:19 -08:00
// check if caller has required role
if !userInfo.Roles.SubmissionReview {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 18:57:42 -08:00
// transaction
2024-11-27 18:19:19 -08:00
smap := datastore.Optional()
2024-12-12 17:29:20 -05:00
smap.Add("status_id", model.StatusRejected)
2024-11-27 18:57:42 -08:00
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted}, smap)
}
2024-12-12 17:29:20 -05:00
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
//
// Role Reviewer changes status from Validated|Accepted|Submitted -> ChangesRequested.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/status/request-changes
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params api.ActionSubmissionRequestChangesParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
2024-11-27 18:19:19 -08:00
// check if caller has required role
if !userInfo.Roles.SubmissionReview {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 18:57:42 -08:00
// transaction
2024-11-27 18:19:19 -08:00
smap := datastore.Optional()
2024-12-12 17:29:20 -05:00
smap.Add("status_id", model.StatusChangesRequested)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidated, model.StatusAccepted, model.StatusSubmitted}, smap)
}
2024-12-12 17:29:20 -05:00
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
//
// Role Submitter changes status from Submitted|ChangesRequested -> UnderConstruction.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/status/revoke
func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.ActionSubmissionRevokeParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
// read submission (this could be done with a transaction WHERE clause)
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 13:58:47 -08:00
return err
}
// check if caller is the submitter
if !userInfo.IsSubmitter(uint64(submission.Submitter)) {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 18:57:42 -08:00
// transaction
2024-11-27 18:19:19 -08:00
smap := datastore.Optional()
2024-12-12 17:29:20 -05:00
smap.Add("status_id", model.StatusUnderConstruction)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusChangesRequested}, smap)
}
2024-12-12 17:29:20 -05:00
// ActionSubmissionSubmit invokes actionSubmissionSubmit operation.
//
// Role Submitter changes status from UnderConstruction|ChangesRequested -> Submitted.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/status/submit
func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.ActionSubmissionSubmitParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
// read submission (this could be done with a transaction WHERE clause)
submission, err := svc.DB.Submissions().Get(ctx, params.SubmissionID)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 13:58:47 -08:00
return err
}
// check if caller is the submitter
if !userInfo.IsSubmitter(uint64(submission.Submitter)) {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 18:57:42 -08:00
// transaction
2024-11-27 18:19:19 -08:00
smap := datastore.Optional()
2024-12-12 17:29:20 -05:00
smap.Add("status_id", model.StatusSubmitted)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction, model.StatusChangesRequested}, smap)
}
2024-12-12 17:29:20 -05:00
2024-12-14 04:06:49 -08:00
// ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
//
2024-12-14 04:06:49 -08:00
// Role Admin changes status from Validated -> Uploading.
//
2024-12-14 04:06:49 -08:00
// POST /submissions/{SubmissionID}/status/trigger-upload
func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params api.ActionSubmissionTriggerUploadParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
2024-11-27 18:19:19 -08:00
// check if caller has required role
if !userInfo.Roles.SubmissionRelease {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 18:57:42 -08:00
// transaction
2024-11-27 18:19:19 -08:00
smap := datastore.Optional()
2024-12-14 04:06:49 -08:00
smap.Add("status_id", model.StatusUploading)
2024-11-29 15:52:44 -08:00
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, smap)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 15:52:44 -08:00
return err
}
// sentinel value because we are not using rust
2024-12-12 17:29:20 -05:00
if submission.TargetAssetID == 0 {
2024-11-29 15:52:44 -08:00
// this is a new map
publish_new_request := model.PublishNewRequest{
SubmissionID: submission.ID,
ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion,
2024-12-15 00:21:01 -08:00
// publish as displayname, whatever
ModelName: submission.DisplayName,
2024-11-29 15:52:44 -08:00
}
j, err := json.Marshal(publish_new_request)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 15:52:44 -08:00
return err
}
svc.Nats.Publish("maptest.submissions.publishnew", []byte(j))
2024-12-12 17:29:20 -05:00
} else {
2024-11-29 15:52:44 -08:00
// this is a map fix
publish_fix_request := model.PublishFixRequest{
SubmissionID: submission.ID,
ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion,
TargetAssetID: submission.TargetAssetID,
}
j, err := json.Marshal(publish_fix_request)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 15:52:44 -08:00
return err
}
svc.Nats.Publish("maptest.submissions.publishfix", []byte(j))
2024-11-29 15:52:44 -08:00
}
return nil
}
2024-12-12 17:29:20 -05:00
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
//
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating.
//
2024-12-09 22:09:52 -08:00
// POST /submissions/{SubmissionID}/status/trigger-validate
func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params api.ActionSubmissionTriggerValidateParams) error {
2024-12-09 21:19:38 -08:00
userInfo, ok := ctx.Value("UserInfo").(UserInfo)
2024-12-12 17:29:20 -05:00
if !ok {
2024-11-29 13:58:47 -08:00
return ErrUserInfo
}
2024-11-27 18:19:19 -08:00
// check if caller has required role
if !userInfo.Roles.SubmissionReview {
2024-11-29 13:58:47 -08:00
return ErrPermissionDenied
}
2024-11-27 18:57:42 -08:00
// transaction
2024-11-27 18:19:19 -08:00
smap := datastore.Optional()
2024-12-12 17:29:20 -05:00
smap.Add("status_id", model.StatusValidating)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusAccepted}, smap)
if err != nil {
2024-11-29 15:52:44 -08:00
return err
}
validate_request := model.ValidateRequest{
SubmissionID: submission.ID,
ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion,
ValidatedModelID: 0, //TODO: reuse velidation models
}
j, err := json.Marshal(validate_request)
2024-12-12 17:29:20 -05:00
if err != nil {
2024-11-29 15:52:44 -08:00
return err
}
2024-12-11 21:07:51 -08:00
svc.Nats.Publish("maptest.submissions.validate", []byte(j))
2024-11-29 15:52:44 -08:00
return nil
}