2 Commits

Author SHA1 Message Date
a5711cdc30 backend: test for exact DisplayName match
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
- The ListMaps method is unexpectedly finding DisplayName substrings
2026-03-24 19:39:27 -07:00
4a399cc2ea format
Some checks failed
continuous-integration/drone/push Build is failing
2026-03-24 19:37:53 -07:00

View File

@@ -12,35 +12,35 @@ import (
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
var(
CreationPhaseSubmissionsLimit = 20
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
CreateSubmissionRateLimit int64 = 5
CreateSubmissionRecencyWindow = time.Second * 600
)
var (
ErrCreationPhaseSubmissionsLimit = fmt.Errorf("%w: Active submissions limited to 20", ErrPermissionDenied)
ErrUploadedAssetIDAlreadyExists = fmt.Errorf("%w: The submission UploadedAssetID is already set", ErrPermissionDenied)
ErrReleaseInvalidStatus = fmt.Errorf("%w: Only submissions with Uploaded status can be released", ErrPermissionDenied)
ErrReleaseNoUploadedAssetID = fmt.Errorf("%w: Only submissions with a UploadedAssetID can be released", ErrPermissionDenied)
ErrAcceptOwnSubmission = fmt.Errorf("%w: You cannot accept your own submission as the submitter", ErrPermissionDenied)
ErrCreateSubmissionRateLimit = fmt.Errorf("%w: You must not create more than 5 submissions every 10 minutes", ErrTooManyRequests)
ErrDisplayNameNotUnique = fmt.Errorf("%w: Cannot submit: A map exists with the same DisplayName", ErrPermissionDenied)
ErrUploadedAssetIDAlreadyExists = fmt.Errorf("%w: The submission UploadedAssetID is already set", ErrPermissionDenied)
ErrReleaseInvalidStatus = fmt.Errorf("%w: Only submissions with Uploaded status can be released", ErrPermissionDenied)
ErrReleaseNoUploadedAssetID = fmt.Errorf("%w: Only submissions with a UploadedAssetID can be released", ErrPermissionDenied)
ErrAcceptOwnSubmission = fmt.Errorf("%w: You cannot accept your own submission as the submitter", ErrPermissionDenied)
ErrCreateSubmissionRateLimit = fmt.Errorf("%w: You must not create more than 5 submissions every 10 minutes", ErrTooManyRequests)
ErrDisplayNameNotUnique = fmt.Errorf("%w: Cannot submit: A map exists with the same DisplayName", ErrPermissionDenied)
)
// POST /submissions
func (svc *Service) CreateSubmission(ctx context.Context, request *api.SubmissionTriggerCreate) (*api.OperationID, error) {
// sanitization
if request.AssetID<0{
if request.AssetID < 0 {
return nil, ErrNegativeID
}
var ModelID=uint64(request.AssetID);
var ModelID = uint64(request.AssetID)
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
@@ -60,7 +60,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
creation_submissions, err := svc.inner.ListSubmissions(ctx, filter, model.Page{
Number: 1,
Size: int32(CreationPhaseSubmissionsLimit),
},datastore.ListSortDisabled)
}, datastore.ListSortDisabled)
if err != nil {
return nil, err
}
@@ -86,8 +86,8 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
}
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
Owner: userId,
StatusID: model.OperationStatusCreated,
Owner: userId,
StatusID: model.OperationStatusCreated,
})
if err != nil {
return nil, err
@@ -110,13 +110,14 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
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{
if request.AssetID < 0 {
return nil, ErrNegativeID
}
var ModelID=uint64(request.AssetID);
var ModelID = uint64(request.AssetID)
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
@@ -134,7 +135,7 @@ func (svc *Service) CreateSubmissionAdmin(ctx context.Context, request *api.Subm
}
// check if caller has required role
has_role := roles & model.RolesSubmissionReview == model.RolesSubmissionReview
has_role := roles&model.RolesSubmissionReview == model.RolesSubmissionReview
if !has_role {
return nil, ErrPermissionDeniedNeedRoleSubmissionReview
}
@@ -155,8 +156,8 @@ func (svc *Service) CreateSubmissionAdmin(ctx context.Context, request *api.Subm
}
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
Owner: userId,
StatusID: model.OperationStatusCreated,
Owner: userId,
StatusID: model.OperationStatusCreated,
})
if err != nil {
return nil, err
@@ -191,18 +192,18 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
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,
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),
StatusID: int32(submission.StatusID),
}, nil
}
@@ -214,28 +215,28 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
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{
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{
if creator, creator_ok := params.Creator.Get(); creator_ok {
filter.SetCreator(creator)
}
if game_id, game_id_ok := params.GameID.Get(); game_id_ok{
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{
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{
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{
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{
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{
if status_id, status_id_ok := params.StatusID.Get(); status_id_ok {
filter.SetStatuses([]model.SubmissionStatus{model.SubmissionStatus(status_id)})
}
@@ -244,27 +245,27 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
total, items, err := svc.inner.ListSubmissionsWithTotal(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
},sort)
}, sort)
if err != nil {
return nil, err
}
var resp api.Submissions
resp.Total=total
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,
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),
StatusID: int32(item.StatusID),
})
}
@@ -341,9 +342,9 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
}
event_data := model.AuditEventDataChangeModel{
OldModelID: OldModelID,
OldModelID: OldModelID,
OldModelVersion: OldModelVersion,
NewModelID: NewModelID,
NewModelID: NewModelID,
NewModelVersion: NewModelVersion,
}
@@ -351,7 +352,7 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -401,7 +402,7 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -456,7 +457,7 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -509,7 +510,7 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -566,8 +567,12 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap
if err != nil {
return err
}
if len(maps_list) != 0 {
return ErrDisplayNameNotUnique
// The map search finds substrings, we only want exact matches
for _, m := range maps_list {
if m.DisplayName == submission.DisplayName {
return ErrDisplayNameNotUnique
}
}
// transaction
@@ -601,7 +606,7 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -672,7 +677,7 @@ func (svc *Service) ActionSubmissionTriggerSubmitUnchecked(ctx context.Context,
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -700,7 +705,7 @@ func (svc *Service) ActionSubmissionResetSubmitting(ctx context.Context, params
if err != nil {
return err
}
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
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
}
@@ -729,7 +734,7 @@ func (svc *Service) ActionSubmissionResetSubmitting(ctx context.Context, params
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -797,7 +802,7 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -834,7 +839,7 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac
if err != nil {
return err
}
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
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
}
@@ -857,7 +862,7 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -929,7 +934,7 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -989,7 +994,7 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -1026,7 +1031,7 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act
if err != nil {
return err
}
if time.Now().Before(submission.UpdatedAt.Add(time.Second*10)) {
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
}
@@ -1049,7 +1054,7 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -1096,11 +1101,11 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
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{
for _, submission := range submissions {
if submission.StatusID != model.SubmissionStatusUploaded {
return nil, ErrReleaseInvalidStatus
}
if submission.UploadedAssetID == 0{
if submission.UploadedAssetID == 0 {
return nil, ErrReleaseNoUploadedAssetID
}
id_to_submission[submission.ID] = &submission
@@ -1126,8 +1131,8 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
// create a trackable long-running operation
operation, err := svc.inner.CreateOperation(ctx, model.Operation{
Owner: userId,
StatusID: model.OperationStatusCreated,
Owner: userId,
StatusID: model.OperationStatusCreated,
})
if err != nil {
return nil, err
@@ -1149,10 +1154,10 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
// CreateSubmissionAuditComment implements createSubmissionAuditComment operation.
//
// Post a comment to the audit log
// # 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) {
func (svc *Service) CreateSubmissionAuditComment(ctx context.Context, req api.CreateSubmissionAuditCommentReq, params api.CreateSubmissionAuditCommentParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
@@ -1193,7 +1198,7 @@ func (svc *Service) CreateSubmissionAuditComment(ctx context.Context, req api.Cr
ctx,
userId,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
event_data,
@@ -1209,7 +1214,7 @@ func (svc *Service) ListSubmissionAuditEvents(ctx context.Context, params api.Li
return svc.inner.ListAuditEvents(
ctx,
model.Resource{
ID: params.SubmissionID,
ID: params.SubmissionID,
Type: model.ResourceSubmission,
},
model.Page{