Prepare To Implement Mapfixes Separately #52

Merged
Quaternions merged 18 commits from separate-mapfixes into staging 2025-04-01 02:27:51 +00:00
35 changed files with 775 additions and 599 deletions

@ -85,8 +85,9 @@ paths:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: TargetAssetID
- name: UploadedAssetID
in: query
required: true
schema:
type: integer
format: int64
@ -303,7 +304,8 @@ components:
- Name
- Hash
- Source
- SubmissionID
- ResourceType
- ResourceID
type: object
properties:
ID:
@ -319,14 +321,18 @@ components:
Source:
type: string
maxLength: 1048576
SubmissionID:
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptCreate:
required:
- Name
- Source
# - SubmissionID
- ResourceType
# - ResourceID
type: object
properties:
Name:
@ -335,7 +341,10 @@ components:
Source:
type: string
maxLength: 1048576
SubmissionID:
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptPolicy:

@ -737,8 +737,7 @@ components:
- AssetID
- AssetVersion
- Completed
- SubmissionType
# - TargetAssetID
# - UploadedAssetID
- StatusID
- StatusMessage
type: object
@ -772,10 +771,7 @@ components:
format: int64
Completed:
type: boolean
SubmissionType:
type: integer
format: int32
TargetAssetID:
UploadedAssetID:
type: integer
format: int64
StatusID:
@ -791,7 +787,6 @@ components:
- GameID
- AssetID
- AssetVersion
# - TargetAssetID
type: object
properties:
DisplayName:
@ -809,9 +804,6 @@ components:
AssetVersion:
type: integer
format: int64
TargetAssetID:
type: integer
format: int64
ReleaseInfo:
required:
- SubmissionID
@ -830,7 +822,8 @@ components:
- Name
- Hash
- Source
- SubmissionID
- ResourceType
- ResourceID
type: object
properties:
ID:
@ -846,14 +839,18 @@ components:
Source:
type: string
maxLength: 1048576
SubmissionID:
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptCreate:
required:
- Name
- Source
# - SubmissionID
- ResourceType
# - ResourceID
type: object
properties:
Name:
@ -862,7 +859,10 @@ components:
Source:
type: string
maxLength: 1048576
SubmissionID:
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptUpdate:
@ -879,7 +879,10 @@ components:
Source:
type: string
maxLength: 1048576
SubmissionID:
ResourceType:
type: integer
format: int32
ResourceID:
type: integer
format: int64
ScriptPolicy:

@ -562,17 +562,22 @@ func (s *Script) encodeFields(e *jx.Encoder) {
e.Str(s.Source)
}
{
e.FieldStart("SubmissionID")
e.Int64(s.SubmissionID)
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
e.FieldStart("ResourceID")
e.Int64(s.ResourceID)
}
}
var jsonFieldsNameOfScript = [5]string{
var jsonFieldsNameOfScript = [6]string{
0: "ID",
1: "Name",
2: "Hash",
3: "Source",
4: "SubmissionID",
4: "ResourceType",
5: "ResourceID",
}
// Decode decodes Script from json.
@ -632,17 +637,29 @@ func (s *Script) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
case "SubmissionID":
case "ResourceType":
requiredBitSet[0] |= 1 << 4
if err := func() error {
v, err := d.Int64()
s.SubmissionID = int64(v)
v, err := d.Int32()
s.ResourceType = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"SubmissionID\"")
return errors.Wrap(err, "decode field \"ResourceType\"")
}
case "ResourceID":
requiredBitSet[0] |= 1 << 5
if err := func() error {
v, err := d.Int64()
s.ResourceID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
default:
return d.Skip()
@ -654,7 +671,7 @@ func (s *Script) Decode(d *jx.Decoder) error {
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00011111,
0b00111111,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
@ -718,17 +735,22 @@ func (s *ScriptCreate) encodeFields(e *jx.Encoder) {
e.Str(s.Source)
}
{
if s.SubmissionID.Set {
e.FieldStart("SubmissionID")
s.SubmissionID.Encode(e)
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
if s.ResourceID.Set {
e.FieldStart("ResourceID")
s.ResourceID.Encode(e)
}
}
}
var jsonFieldsNameOfScriptCreate = [3]string{
var jsonFieldsNameOfScriptCreate = [4]string{
0: "Name",
1: "Source",
2: "SubmissionID",
2: "ResourceType",
3: "ResourceID",
}
// Decode decodes ScriptCreate from json.
@ -764,15 +786,27 @@ func (s *ScriptCreate) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
case "SubmissionID":
case "ResourceType":
requiredBitSet[0] |= 1 << 2
if err := func() error {
s.SubmissionID.Reset()
if err := s.SubmissionID.Decode(d); err != nil {
v, err := d.Int32()
s.ResourceType = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"SubmissionID\"")
return errors.Wrap(err, "decode field \"ResourceType\"")
}
case "ResourceID":
if err := func() error {
s.ResourceID.Reset()
if err := s.ResourceID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
default:
return d.Skip()
@ -784,7 +818,7 @@ func (s *ScriptCreate) Decode(d *jx.Decoder) error {
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000011,
0b00000111,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
@ -1280,18 +1314,25 @@ func (s *ScriptUpdate) encodeFields(e *jx.Encoder) {
}
}
{
if s.SubmissionID.Set {
e.FieldStart("SubmissionID")
s.SubmissionID.Encode(e)
if s.ResourceType.Set {
e.FieldStart("ResourceType")
s.ResourceType.Encode(e)
}
}
{
if s.ResourceID.Set {
e.FieldStart("ResourceID")
s.ResourceID.Encode(e)
}
}
}
var jsonFieldsNameOfScriptUpdate = [4]string{
var jsonFieldsNameOfScriptUpdate = [5]string{
0: "ID",
1: "Name",
2: "Source",
3: "SubmissionID",
3: "ResourceType",
4: "ResourceID",
}
// Decode decodes ScriptUpdate from json.
@ -1335,15 +1376,25 @@ func (s *ScriptUpdate) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
case "SubmissionID":
case "ResourceType":
if err := func() error {
s.SubmissionID.Reset()
if err := s.SubmissionID.Decode(d); err != nil {
s.ResourceType.Reset()
if err := s.ResourceType.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"SubmissionID\"")
return errors.Wrap(err, "decode field \"ResourceType\"")
}
case "ResourceID":
if err := func() error {
s.ResourceID.Reset()
if err := s.ResourceID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
default:
return d.Skip()
@ -1451,13 +1502,9 @@ func (s *Submission) encodeFields(e *jx.Encoder) {
e.Bool(s.Completed)
}
{
e.FieldStart("SubmissionType")
e.Int32(s.SubmissionType)
}
{
if s.TargetAssetID.Set {
e.FieldStart("TargetAssetID")
s.TargetAssetID.Encode(e)
if s.UploadedAssetID.Set {
e.FieldStart("UploadedAssetID")
s.UploadedAssetID.Encode(e)
}
}
{
@ -1470,7 +1517,7 @@ func (s *Submission) encodeFields(e *jx.Encoder) {
}
}
var jsonFieldsNameOfSubmission = [14]string{
var jsonFieldsNameOfSubmission = [13]string{
0: "ID",
1: "DisplayName",
2: "Creator",
@ -1481,10 +1528,9 @@ var jsonFieldsNameOfSubmission = [14]string{
7: "AssetID",
8: "AssetVersion",
9: "Completed",
10: "SubmissionType",
11: "TargetAssetID",
12: "StatusID",
13: "StatusMessage",
10: "UploadedAssetID",
11: "StatusID",
12: "StatusMessage",
}
// Decode decodes Submission from json.
@ -1616,30 +1662,18 @@ func (s *Submission) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"Completed\"")
}
case "SubmissionType":
requiredBitSet[1] |= 1 << 2
case "UploadedAssetID":
if err := func() error {
v, err := d.Int32()
s.SubmissionType = int32(v)
if err != nil {
s.UploadedAssetID.Reset()
if err := s.UploadedAssetID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"SubmissionType\"")
}
case "TargetAssetID":
if err := func() error {
s.TargetAssetID.Reset()
if err := s.TargetAssetID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"TargetAssetID\"")
return errors.Wrap(err, "decode field \"UploadedAssetID\"")
}
case "StatusID":
requiredBitSet[1] |= 1 << 4
requiredBitSet[1] |= 1 << 3
if err := func() error {
v, err := d.Int32()
s.StatusID = int32(v)
@ -1651,7 +1685,7 @@ func (s *Submission) Decode(d *jx.Decoder) error {
return errors.Wrap(err, "decode field \"StatusID\"")
}
case "StatusMessage":
requiredBitSet[1] |= 1 << 5
requiredBitSet[1] |= 1 << 4
if err := func() error {
v, err := d.Str()
s.StatusMessage = string(v)
@ -1673,7 +1707,7 @@ func (s *Submission) Decode(d *jx.Decoder) error {
var failures []validate.FieldError
for i, mask := range [2]uint8{
0b11111111,
0b00110111,
0b00011011,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
@ -1748,21 +1782,14 @@ func (s *SubmissionCreate) encodeFields(e *jx.Encoder) {
e.FieldStart("AssetVersion")
e.Int64(s.AssetVersion)
}
{
if s.TargetAssetID.Set {
e.FieldStart("TargetAssetID")
s.TargetAssetID.Encode(e)
}
}
}
var jsonFieldsNameOfSubmissionCreate = [6]string{
var jsonFieldsNameOfSubmissionCreate = [5]string{
0: "DisplayName",
1: "Creator",
2: "GameID",
3: "AssetID",
4: "AssetVersion",
5: "TargetAssetID",
}
// Decode decodes SubmissionCreate from json.
@ -1834,16 +1861,6 @@ func (s *SubmissionCreate) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"AssetVersion\"")
}
case "TargetAssetID":
if err := func() error {
s.TargetAssetID.Reset()
if err := s.TargetAssetID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"TargetAssetID\"")
}
default:
return d.Skip()
}

@ -314,7 +314,8 @@ type Script struct {
Name string `json:"Name"`
Hash string `json:"Hash"`
Source string `json:"Source"`
SubmissionID int64 `json:"SubmissionID"`
ResourceType int32 `json:"ResourceType"`
ResourceID int64 `json:"ResourceID"`
}
// GetID returns the value of ID.
@ -337,9 +338,14 @@ func (s *Script) GetSource() string {
return s.Source
}
// GetSubmissionID returns the value of SubmissionID.
func (s *Script) GetSubmissionID() int64 {
return s.SubmissionID
// GetResourceType returns the value of ResourceType.
func (s *Script) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *Script) GetResourceID() int64 {
return s.ResourceID
}
// SetID sets the value of ID.
@ -362,16 +368,22 @@ func (s *Script) SetSource(val string) {
s.Source = val
}
// SetSubmissionID sets the value of SubmissionID.
func (s *Script) SetSubmissionID(val int64) {
s.SubmissionID = val
// SetResourceType sets the value of ResourceType.
func (s *Script) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *Script) SetResourceID(val int64) {
s.ResourceID = val
}
// Ref: #/components/schemas/ScriptCreate
type ScriptCreate struct {
Name string `json:"Name"`
Source string `json:"Source"`
SubmissionID OptInt64 `json:"SubmissionID"`
ResourceType int32 `json:"ResourceType"`
ResourceID OptInt64 `json:"ResourceID"`
}
// GetName returns the value of Name.
@ -384,9 +396,14 @@ func (s *ScriptCreate) GetSource() string {
return s.Source
}
// GetSubmissionID returns the value of SubmissionID.
func (s *ScriptCreate) GetSubmissionID() OptInt64 {
return s.SubmissionID
// GetResourceType returns the value of ResourceType.
func (s *ScriptCreate) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *ScriptCreate) GetResourceID() OptInt64 {
return s.ResourceID
}
// SetName sets the value of Name.
@ -399,9 +416,14 @@ func (s *ScriptCreate) SetSource(val string) {
s.Source = val
}
// SetSubmissionID sets the value of SubmissionID.
func (s *ScriptCreate) SetSubmissionID(val OptInt64) {
s.SubmissionID = val
// SetResourceType sets the value of ResourceType.
func (s *ScriptCreate) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *ScriptCreate) SetResourceID(val OptInt64) {
s.ResourceID = val
}
// Ref: #/components/schemas/ScriptPolicy
@ -542,7 +564,8 @@ type ScriptUpdate struct {
ID int64 `json:"ID"`
Name OptString `json:"Name"`
Source OptString `json:"Source"`
SubmissionID OptInt64 `json:"SubmissionID"`
ResourceType OptInt32 `json:"ResourceType"`
ResourceID OptInt64 `json:"ResourceID"`
}
// GetID returns the value of ID.
@ -560,9 +583,14 @@ func (s *ScriptUpdate) GetSource() OptString {
return s.Source
}
// GetSubmissionID returns the value of SubmissionID.
func (s *ScriptUpdate) GetSubmissionID() OptInt64 {
return s.SubmissionID
// GetResourceType returns the value of ResourceType.
func (s *ScriptUpdate) GetResourceType() OptInt32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *ScriptUpdate) GetResourceID() OptInt64 {
return s.ResourceID
}
// SetID sets the value of ID.
@ -580,9 +608,14 @@ func (s *ScriptUpdate) SetSource(val OptString) {
s.Source = val
}
// SetSubmissionID sets the value of SubmissionID.
func (s *ScriptUpdate) SetSubmissionID(val OptInt64) {
s.SubmissionID = val
// SetResourceType sets the value of ResourceType.
func (s *ScriptUpdate) SetResourceType(val OptInt32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *ScriptUpdate) SetResourceID(val OptInt64) {
s.ResourceID = val
}
// SetSubmissionCompletedNoContent is response for SetSubmissionCompleted operation.
@ -590,20 +623,19 @@ type SetSubmissionCompletedNoContent struct{}
// Ref: #/components/schemas/Submission
type Submission struct {
ID int64 `json:"ID"`
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
CreatedAt int64 `json:"CreatedAt"`
UpdatedAt int64 `json:"UpdatedAt"`
Submitter int64 `json:"Submitter"`
AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"`
Completed bool `json:"Completed"`
SubmissionType int32 `json:"SubmissionType"`
TargetAssetID OptInt64 `json:"TargetAssetID"`
StatusID int32 `json:"StatusID"`
StatusMessage string `json:"StatusMessage"`
ID int64 `json:"ID"`
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
CreatedAt int64 `json:"CreatedAt"`
UpdatedAt int64 `json:"UpdatedAt"`
Submitter int64 `json:"Submitter"`
AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"`
Completed bool `json:"Completed"`
UploadedAssetID OptInt64 `json:"UploadedAssetID"`
StatusID int32 `json:"StatusID"`
StatusMessage string `json:"StatusMessage"`
}
// GetID returns the value of ID.
@ -656,14 +688,9 @@ func (s *Submission) GetCompleted() bool {
return s.Completed
}
// GetSubmissionType returns the value of SubmissionType.
func (s *Submission) GetSubmissionType() int32 {
return s.SubmissionType
}
// GetTargetAssetID returns the value of TargetAssetID.
func (s *Submission) GetTargetAssetID() OptInt64 {
return s.TargetAssetID
// GetUploadedAssetID returns the value of UploadedAssetID.
func (s *Submission) GetUploadedAssetID() OptInt64 {
return s.UploadedAssetID
}
// GetStatusID returns the value of StatusID.
@ -726,14 +753,9 @@ func (s *Submission) SetCompleted(val bool) {
s.Completed = val
}
// SetSubmissionType sets the value of SubmissionType.
func (s *Submission) SetSubmissionType(val int32) {
s.SubmissionType = val
}
// SetTargetAssetID sets the value of TargetAssetID.
func (s *Submission) SetTargetAssetID(val OptInt64) {
s.TargetAssetID = val
// SetUploadedAssetID sets the value of UploadedAssetID.
func (s *Submission) SetUploadedAssetID(val OptInt64) {
s.UploadedAssetID = val
}
// SetStatusID sets the value of StatusID.
@ -748,12 +770,11 @@ func (s *Submission) SetStatusMessage(val string) {
// Ref: #/components/schemas/SubmissionCreate
type SubmissionCreate struct {
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"`
TargetAssetID OptInt64 `json:"TargetAssetID"`
DisplayName string `json:"DisplayName"`
Creator string `json:"Creator"`
GameID int32 `json:"GameID"`
AssetID int64 `json:"AssetID"`
AssetVersion int64 `json:"AssetVersion"`
}
// GetDisplayName returns the value of DisplayName.
@ -781,11 +802,6 @@ func (s *SubmissionCreate) GetAssetVersion() int64 {
return s.AssetVersion
}
// GetTargetAssetID returns the value of TargetAssetID.
func (s *SubmissionCreate) GetTargetAssetID() OptInt64 {
return s.TargetAssetID
}
// SetDisplayName sets the value of DisplayName.
func (s *SubmissionCreate) SetDisplayName(val string) {
s.DisplayName = val
@ -811,11 +827,6 @@ func (s *SubmissionCreate) SetAssetVersion(val int64) {
s.AssetVersion = val
}
// SetTargetAssetID sets the value of TargetAssetID.
func (s *SubmissionCreate) SetTargetAssetID(val OptInt64) {
s.TargetAssetID = val
}
// UpdateScriptNoContent is response for UpdateScript operation.
type UpdateScriptNoContent struct{}

@ -9,7 +9,16 @@ import (
var (
ErrNotExist = errors.New("resource does not exist")
ErroNoRowsAffected = errors.New("query did not affect any rows")
ErrInvalidSubmissionListSort = errors.New("invalid submission list sort parameter [1,2,3,4]")
ErrInvalidListSort = errors.New("invalid list sort parameter [1,2,3,4]")
)
type ListSort uint32
const (
ListSortDisabled ListSort = 0
ListSortDisplayNameAscending ListSort = 1
ListSortDisplayNameDescending ListSort = 2
ListSortDateAscending ListSort = 3
ListSortDateDescending ListSort = 4
)
type Datastore interface {
@ -23,10 +32,10 @@ type Submissions interface {
GetList(ctx context.Context, id []int64) ([]model.Submission, error)
Create(ctx context.Context, smap model.Submission) (model.Submission, error)
Update(ctx context.Context, id int64, values OptionalMap) error
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values OptionalMap) error
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.Status, values OptionalMap) (model.Submission, error)
IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) error
IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.SubmissionStatus, values OptionalMap) (model.Submission, error)
Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page, sort model.SubmissionListSort) ([]model.Submission, error)
List(ctx context.Context, filters OptionalMap, page model.Page, sort ListSort) ([]model.Submission, error)
}
type Scripts interface {

@ -53,7 +53,7 @@ func (env *Scripts) Update(ctx context.Context, id int64, values datastore.Optio
}
// the update can only occur if the status matches one of the provided values.
func (env *Scripts) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) error {
func (env *Scripts) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) error {
if err := env.db.Model(&model.Script{}).Where("id = ?", id).Where("status IN ?", statuses).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist

@ -54,7 +54,7 @@ func (env *Submissions) Update(ctx context.Context, id int64, values datastore.O
}
// the update can only occur if the status matches one of the provided values.
func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) error {
func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) error {
if err := env.db.Model(&model.Submission{}).Where("id = ?", id).Where("status_id IN ?", statuses).Updates(values.Map()).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return datastore.ErrNotExist
@ -67,7 +67,7 @@ func (env *Submissions) IfStatusThenUpdate(ctx context.Context, id int64, status
// the update can only occur if the status matches one of the provided values.
// returns the updated value
func (env *Submissions) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.Status, values datastore.OptionalMap) (model.Submission, error) {
func (env *Submissions) IfStatusThenUpdateAndGet(ctx context.Context, id int64, statuses []model.SubmissionStatus, values datastore.OptionalMap) (model.Submission, error) {
var submission model.Submission
result := env.db.Model(&submission).
Clauses(clause.Returning{}).
@ -99,29 +99,29 @@ func (env *Submissions) Delete(ctx context.Context, id int64) error {
return nil
}
func (env *Submissions) List(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort model.SubmissionListSort) ([]model.Submission, error) {
func (env *Submissions) List(ctx context.Context, filters datastore.OptionalMap, page model.Page, sort datastore.ListSort) ([]model.Submission, error) {
var maps []model.Submission
db := env.db
switch sort {
case model.SubmissionListSortDisabled:
case datastore.ListSortDisabled:
// No sort
break
case model.SubmissionListSortDisplayNameAscending:
case datastore.ListSortDisplayNameAscending:
db=db.Order("display_name ASC")
break
case model.SubmissionListSortDisplayNameDescending:
case datastore.ListSortDisplayNameDescending:
db=db.Order("display_name DESC")
break
case model.SubmissionListSortDateAscending:
case datastore.ListSortDateAscending:
db=db.Order("created_at ASC")
break
case model.SubmissionListSortDateDescending:
case datastore.ListSortDateDescending:
db=db.Order("created_at DESC")
break
default:
return nil, datastore.ErrInvalidSubmissionListSort
return nil, datastore.ErrInvalidListSort
}
if err := db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&maps).Error; err != nil {

@ -312,18 +312,15 @@ func (c *Client) sendActionSubmissionUploaded(ctx context.Context, params Action
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "TargetAssetID" parameter.
// Encode "UploadedAssetID" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "TargetAssetID",
Name: "UploadedAssetID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := params.TargetAssetID.Get(); ok {
return e.EncodeValue(conv.Int64ToString(val))
}
return nil
return e.EncodeValue(conv.Int64ToString(params.UploadedAssetID))
}); err != nil {
return res, errors.Wrap(err, "encode query")
}

@ -282,9 +282,9 @@ func (s *Server) handleActionSubmissionUploadedRequest(args [1]string, argsEscap
In: "path",
}: params.SubmissionID,
{
Name: "TargetAssetID",
Name: "UploadedAssetID",
In: "query",
}: params.TargetAssetID,
}: params.UploadedAssetID,
},
Raw: r,
}

@ -282,17 +282,22 @@ func (s *Script) encodeFields(e *jx.Encoder) {
e.Str(s.Source)
}
{
e.FieldStart("SubmissionID")
e.Int64(s.SubmissionID)
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
e.FieldStart("ResourceID")
e.Int64(s.ResourceID)
}
}
var jsonFieldsNameOfScript = [5]string{
var jsonFieldsNameOfScript = [6]string{
0: "ID",
1: "Name",
2: "Hash",
3: "Source",
4: "SubmissionID",
4: "ResourceType",
5: "ResourceID",
}
// Decode decodes Script from json.
@ -352,17 +357,29 @@ func (s *Script) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
case "SubmissionID":
case "ResourceType":
requiredBitSet[0] |= 1 << 4
if err := func() error {
v, err := d.Int64()
s.SubmissionID = int64(v)
v, err := d.Int32()
s.ResourceType = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"SubmissionID\"")
return errors.Wrap(err, "decode field \"ResourceType\"")
}
case "ResourceID":
requiredBitSet[0] |= 1 << 5
if err := func() error {
v, err := d.Int64()
s.ResourceID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
default:
return d.Skip()
@ -374,7 +391,7 @@ func (s *Script) Decode(d *jx.Decoder) error {
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00011111,
0b00111111,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
@ -438,17 +455,22 @@ func (s *ScriptCreate) encodeFields(e *jx.Encoder) {
e.Str(s.Source)
}
{
if s.SubmissionID.Set {
e.FieldStart("SubmissionID")
s.SubmissionID.Encode(e)
e.FieldStart("ResourceType")
e.Int32(s.ResourceType)
}
{
if s.ResourceID.Set {
e.FieldStart("ResourceID")
s.ResourceID.Encode(e)
}
}
}
var jsonFieldsNameOfScriptCreate = [3]string{
var jsonFieldsNameOfScriptCreate = [4]string{
0: "Name",
1: "Source",
2: "SubmissionID",
2: "ResourceType",
3: "ResourceID",
}
// Decode decodes ScriptCreate from json.
@ -484,15 +506,27 @@ func (s *ScriptCreate) Decode(d *jx.Decoder) error {
}(); err != nil {
return errors.Wrap(err, "decode field \"Source\"")
}
case "SubmissionID":
case "ResourceType":
requiredBitSet[0] |= 1 << 2
if err := func() error {
s.SubmissionID.Reset()
if err := s.SubmissionID.Decode(d); err != nil {
v, err := d.Int32()
s.ResourceType = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"SubmissionID\"")
return errors.Wrap(err, "decode field \"ResourceType\"")
}
case "ResourceID":
if err := func() error {
s.ResourceID.Reset()
if err := s.ResourceID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ResourceID\"")
}
default:
return d.Skip()
@ -504,7 +538,7 @@ func (s *ScriptCreate) Decode(d *jx.Decoder) error {
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000011,
0b00000111,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.

@ -145,8 +145,8 @@ func decodeActionSubmissionAcceptedParams(args [1]string, argsEscaped bool, r *h
// ActionSubmissionUploadedParams is parameters of actionSubmissionUploaded operation.
type ActionSubmissionUploadedParams struct {
// The unique identifier for a submission.
SubmissionID int64
TargetAssetID OptInt64
SubmissionID int64
UploadedAssetID int64
}
func unpackActionSubmissionUploadedParams(packed middleware.Parameters) (params ActionSubmissionUploadedParams) {
@ -159,12 +159,10 @@ func unpackActionSubmissionUploadedParams(packed middleware.Parameters) (params
}
{
key := middleware.ParameterKey{
Name: "TargetAssetID",
Name: "UploadedAssetID",
In: "query",
}
if v, ok := packed[key]; ok {
params.TargetAssetID = v.(OptInt64)
}
params.UploadedAssetID = packed[key].(int64)
}
return params
}
@ -216,43 +214,38 @@ func decodeActionSubmissionUploadedParams(args [1]string, argsEscaped bool, r *h
Err: err,
}
}
// Decode query: TargetAssetID.
// Decode query: UploadedAssetID.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "TargetAssetID",
Name: "UploadedAssetID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
var paramsDotTargetAssetIDVal int64
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
paramsDotTargetAssetIDVal = c
return nil
}(); err != nil {
val, err := d.DecodeValue()
if err != nil {
return err
}
params.TargetAssetID.SetTo(paramsDotTargetAssetIDVal)
c, err := conv.ToInt64(val)
if err != nil {
return err
}
params.UploadedAssetID = c
return nil
}); err != nil {
return err
}
} else {
return err
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "TargetAssetID",
Name: "UploadedAssetID",
In: "query",
Err: err,
}

@ -231,7 +231,8 @@ type Script struct {
Name string `json:"Name"`
Hash string `json:"Hash"`
Source string `json:"Source"`
SubmissionID int64 `json:"SubmissionID"`
ResourceType int32 `json:"ResourceType"`
ResourceID int64 `json:"ResourceID"`
}
// GetID returns the value of ID.
@ -254,9 +255,14 @@ func (s *Script) GetSource() string {
return s.Source
}
// GetSubmissionID returns the value of SubmissionID.
func (s *Script) GetSubmissionID() int64 {
return s.SubmissionID
// GetResourceType returns the value of ResourceType.
func (s *Script) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *Script) GetResourceID() int64 {
return s.ResourceID
}
// SetID sets the value of ID.
@ -279,16 +285,22 @@ func (s *Script) SetSource(val string) {
s.Source = val
}
// SetSubmissionID sets the value of SubmissionID.
func (s *Script) SetSubmissionID(val int64) {
s.SubmissionID = val
// SetResourceType sets the value of ResourceType.
func (s *Script) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *Script) SetResourceID(val int64) {
s.ResourceID = val
}
// Ref: #/components/schemas/ScriptCreate
type ScriptCreate struct {
Name string `json:"Name"`
Source string `json:"Source"`
SubmissionID OptInt64 `json:"SubmissionID"`
ResourceType int32 `json:"ResourceType"`
ResourceID OptInt64 `json:"ResourceID"`
}
// GetName returns the value of Name.
@ -301,9 +313,14 @@ func (s *ScriptCreate) GetSource() string {
return s.Source
}
// GetSubmissionID returns the value of SubmissionID.
func (s *ScriptCreate) GetSubmissionID() OptInt64 {
return s.SubmissionID
// GetResourceType returns the value of ResourceType.
func (s *ScriptCreate) GetResourceType() int32 {
return s.ResourceType
}
// GetResourceID returns the value of ResourceID.
func (s *ScriptCreate) GetResourceID() OptInt64 {
return s.ResourceID
}
// SetName sets the value of Name.
@ -316,9 +333,14 @@ func (s *ScriptCreate) SetSource(val string) {
s.Source = val
}
// SetSubmissionID sets the value of SubmissionID.
func (s *ScriptCreate) SetSubmissionID(val OptInt64) {
s.SubmissionID = val
// SetResourceType sets the value of ResourceType.
func (s *ScriptCreate) SetResourceType(val int32) {
s.ResourceType = val
}
// SetResourceID sets the value of ResourceID.
func (s *ScriptCreate) SetResourceID(val OptInt64) {
s.ResourceID = val
}
// Ref: #/components/schemas/ScriptPolicy

@ -23,12 +23,20 @@ func HashParse(hash string) (uint64, error){
return strconv.ParseUint(hash, 16, 64)
}
type ResourceType int32
const (
ResourceUnknown ResourceType = 0
ResourceMapfix ResourceType = 1
ResourceSubmission ResourceType = 2
)
type Script struct {
ID int64 `gorm:"primaryKey"`
Name string
Hash int64 // postgres does not support unsigned integers, so we have to pretend
Source string
SubmissionID int64 // which submission did this script first appear in
ResourceType ResourceType // is this a submission or is it a mapfix
ResourceID int64 // which submission / mapfix did this script first appear in
CreatedAt time.Time
UpdatedAt time.Time
}

@ -2,34 +2,24 @@ package model
import "time"
type Status int32
type SubmissionStatus int32
const (
// Phase: Final Status
StatusReleased Status = 9
StatusRejected Status = 8
// Phase: Final SubmissionStatus
SubmissionStatusReleased SubmissionStatus = 9
SubmissionStatusRejected SubmissionStatus = 8
// Phase: Testing
StatusUploaded Status = 7 // uploaded to the group, but pending release
StatusUploading Status = 6
StatusValidated Status = 5
StatusValidating Status = 4
StatusAccepted Status = 3 // pending script review, can re-trigger validation
SubmissionStatusUploaded SubmissionStatus = 7 // uploaded to the group, but pending release
SubmissionStatusUploading SubmissionStatus = 6
SubmissionStatusValidated SubmissionStatus = 5
SubmissionStatusValidating SubmissionStatus = 4
SubmissionStatusAccepted SubmissionStatus = 3 // pending script review, can re-trigger validation
// Phase: Creation
StatusChangesRequested Status = 2
StatusSubmitted Status = 1
StatusUnderConstruction Status = 0
)
type SubmissionListSort uint32
const (
SubmissionListSortDisabled SubmissionListSort = 0
SubmissionListSortDisplayNameAscending SubmissionListSort = 1
SubmissionListSortDisplayNameDescending SubmissionListSort = 2
SubmissionListSortDateAscending SubmissionListSort = 3
SubmissionListSortDateDescending SubmissionListSort = 4
SubmissionStatusChangesRequested SubmissionStatus = 2
SubmissionStatusSubmitted SubmissionStatus = 1
SubmissionStatusUnderConstruction SubmissionStatus = 0
)
type Submission struct {
@ -40,16 +30,12 @@ type Submission struct {
CreatedAt time.Time
UpdatedAt time.Time
Submitter int64 // UserID
// Who clicked the Accept button
AcceptedBy int64 // UserID
// Who clicked the Upload button
UploadedBy int64 // UserID
AssetID int64
AssetVersion int64
ValidatedAssetID int64
ValidatedAssetVersion int64
Completed bool // Has this version of the map been completed at least once on maptest
TargetAssetID int64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
StatusID Status
UploadedAssetID int64 // where to upload map fix. if the TargetAssetID is 0, it's a new map.
StatusID SubmissionStatus
StatusMessage string
}

@ -32,7 +32,8 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a
Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
Source: req.Source,
SubmissionID: req.SubmissionID.Or(0),
ResourceType: model.ResourceType(req.ResourceType),
ResourceID: req.ResourceID.Or(0),
})
if err != nil {
return nil, err
@ -82,7 +83,8 @@ func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParam
ID: item.ID,
Hash: model.HashFormat(uint64(item.Hash)),
Source: item.Source,
SubmissionID: item.SubmissionID,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
})
}
@ -134,7 +136,8 @@ func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (
Name: script.Name,
Hash: model.HashFormat(uint64(script.Hash)),
Source: script.Source,
SubmissionID: script.SubmissionID,
ResourceType: int32(script.ResourceType),
ResourceID: script.ResourceID,
}, nil
}
@ -165,8 +168,11 @@ func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, par
pmap.Add("source", source)
pmap.Add("hash", int64(model.HashSource(source))) // No type safety!
}
if SubmissionID, ok := req.SubmissionID.Get(); ok {
pmap.Add("submission_id", SubmissionID)
if ResourceType, ok := req.ResourceType.Get(); ok {
pmap.Add("resource_type", ResourceType)
}
if ResourceID, ok := req.ResourceID.Get(); ok {
pmap.Add("resource_id", ResourceID)
}
return svc.DB.Scripts().Update(ctx, req.ID, pmap)
}

@ -19,8 +19,8 @@ type Roles int32
var (
RolesSubmissionRelease Roles = 1<<4
RolesScriptWrite Roles = 1<<3
RolesSubmissionUpload Roles = 1<<2
RolesSubmissionReview Roles = 1<<1
RolesMapUpload Roles = 1<<2
RolesMapReview Roles = 1<<1
RolesMapDownload Roles = 1<<0
RolesEmpty Roles = 0
)
@ -32,13 +32,13 @@ var (
RoleQuat GroupRole = 255
RoleItzaname GroupRole = 254
RoleStagingDeveloper GroupRole = 240
RolesAll Roles = RolesScriptWrite|RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesMapDownload
RolesAll Roles = RolesScriptWrite|RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload
// has SubmissionUpload
RoleMapAdmin GroupRole = 128
RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesMapDownload
RolesMapAdmin Roles = RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload
// has SubmissionReview
RoleMapCouncil GroupRole = 64
RolesMapCouncil Roles = RolesSubmissionReview|RolesSubmissionUpload|RolesMapDownload
RolesMapCouncil Roles = RolesMapReview|RolesMapUpload|RolesMapDownload
// access to downloading maps
RoleMapAccess GroupRole = 32
RolesMapAccess Roles = RolesMapDownload
@ -128,17 +128,23 @@ func (usr UserInfoHandle) GetRoles() (Roles, error) {
}
// RoleThumbnail
func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) {
return usr.hasRoles(RolesMapUpload)
}
func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) {
return usr.hasRoles(RolesMapReview)
}
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
return usr.hasRoles(RolesMapDownload)
}
func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) {
return usr.hasRoles(RolesSubmissionRelease)
}
func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) {
return usr.hasRoles(RolesSubmissionUpload)
return usr.hasRoles(RolesMapUpload)
}
func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
return usr.hasRoles(RolesSubmissionReview)
}
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
return usr.hasRoles(RolesMapDownload)
return usr.hasRoles(RolesMapReview)
}
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
return usr.hasRoles(RolesScriptWrite)

@ -3,6 +3,7 @@ package service
import (
"context"
"errors"
"fmt"
"git.itzana.me/strafesnet/go-grpc/maps"
"git.itzana.me/strafesnet/maps-service/pkg/api"
@ -15,6 +16,14 @@ var (
ErrPermissionDenied = errors.New("Permission denied")
// ErrUserInfo user info is missing for some reason
ErrUserInfo = errors.New("Missing user info")
ErrDelayReset = errors.New("Please give the validator at least 10 seconds to operate before attempting to reset the status")
ErrPermissionDeniedNotSubmitter = fmt.Errorf("%w: You must be the submitter to perform this action", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionRelease = fmt.Errorf("%w: Need Role SubmissionRelease", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapUpload = fmt.Errorf("%w: Need Role MapUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapReview = fmt.Errorf("%w: Need Role MapReview", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied)
)
type Service struct {

@ -15,45 +15,37 @@ import (
var(
CreationPhaseSubmissionsLimit = 20
CreationPhaseSubmissionStatuses = []model.Status{
model.StatusChangesRequested,
model.StatusSubmitted,
model.StatusUnderConstruction,
CreationPhaseSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusChangesRequested,
model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction,
}
// prevent two mapfixes with same asset id
ActiveSubmissionStatuses = []model.Status{
model.StatusUploading,
model.StatusValidated,
model.StatusValidating,
model.StatusAccepted,
model.StatusChangesRequested,
model.StatusSubmitted,
model.StatusUnderConstruction,
ActiveSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading,
model.SubmissionStatusValidated,
model.SubmissionStatusValidating,
model.SubmissionStatusAccepted,
model.SubmissionStatusChangesRequested,
model.SubmissionStatusSubmitted,
model.SubmissionStatusUnderConstruction,
}
// limit mapfixes in the pipeline to one per target map
ActiveAcceptedSubmissionStatuses = []model.Status{
model.StatusUploading,
model.StatusValidated,
model.StatusValidating,
model.StatusAccepted,
ActiveAcceptedSubmissionStatuses = []model.SubmissionStatus{
model.SubmissionStatusUploading,
model.SubmissionStatusValidated,
model.SubmissionStatusValidating,
model.SubmissionStatusAccepted,
}
)
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")
ErrUploadedAssetIDAlreadyExists = errors.New("The submission UploadedAssetID is already set")
ErrReleaseInvalidStatus = errors.New("Only submissions with Uploaded status can be released")
ErrReleaseNoTargetAssetID = errors.New("Only submissions with a TargetAssetID 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)
ErrDelayReset = errors.New("Please give the validator at least 10 seconds to operate before attempting to reset the status")
ErrPermissionDeniedNotSubmitter = fmt.Errorf("%w: You must be the submitter to perform this action", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionRelease = fmt.Errorf("%w: Need Role SubmissionRelease", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionUpload = fmt.Errorf("%w: Need Role SubmissionUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleSubmissionReview = fmt.Errorf("%w: Need Role SubmissionReview", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapDownload = fmt.Errorf("%w: Need Role MapDownload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleScriptWrite = fmt.Errorf("%w: Need Role ScriptWrite", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMaptest = fmt.Errorf("%w: Need Role Maptest", ErrPermissionDenied)
)
// POST /submissions
@ -76,7 +68,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
creation_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: int32(CreationPhaseSubmissionsLimit),
},model.SubmissionListSortDisabled)
},datastore.ListSortDisabled)
if err != nil {
return nil, err
}
@ -95,7 +87,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: 1,
},model.SubmissionListSortDisabled)
},datastore.ListSortDisabled)
if err != nil {
return nil, err
}
@ -113,8 +105,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *api.Submissio
AssetID: request.AssetID,
AssetVersion: request.AssetVersion,
Completed: false,
TargetAssetID: request.TargetAssetID.Value,
StatusID: model.StatusUnderConstruction,
StatusID: model.SubmissionStatusUnderConstruction,
})
if err != nil {
return nil, err
@ -145,7 +136,7 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
AssetID: int64(submission.AssetID),
AssetVersion: int64(submission.AssetVersion),
Completed: submission.Completed,
TargetAssetID: api.NewOptInt64(int64(submission.TargetAssetID)),
UploadedAssetID: api.NewOptInt64(int64(submission.UploadedAssetID)),
StatusID: int32(submission.StatusID),
StatusMessage: submission.StatusMessage,
}, nil
@ -169,7 +160,7 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
filter.Add("game_id", params.GameID.Value)
}
sort := model.SubmissionListSort(params.Sort.Or(int32(model.SubmissionListSortDisabled)))
sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled)))
items, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: params.Page,
@ -192,7 +183,7 @@ func (svc *Service) ListSubmissions(ctx context.Context, params api.ListSubmissi
AssetID: int64(item.AssetID),
AssetVersion: int64(item.AssetVersion),
Completed: item.Completed,
TargetAssetID: api.NewOptInt64(int64(item.TargetAssetID)),
UploadedAssetID: api.NewOptInt64(int64(item.UploadedAssetID)),
StatusID: int32(item.StatusID),
})
}
@ -257,7 +248,7 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
pmap.AddNotNil("asset_version", params.VersionID)
//always reset completed when model changes
pmap.Add("completed", false)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusChangesRequested, model.StatusSubmitted, model.StatusUnderConstruction}, pmap)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusChangesRequested, model.SubmissionStatusSubmitted, model.SubmissionStatusUnderConstruction}, pmap)
}
// ActionSubmissionReject invokes actionSubmissionReject operation.
@ -277,13 +268,13 @@ func (svc *Service) ActionSubmissionReject(ctx context.Context, params api.Actio
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionReview
return ErrPermissionDeniedNeedRoleMapReview
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusRejected)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted}, smap)
smap.Add("status_id", model.SubmissionStatusRejected)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap)
}
// ActionSubmissionRequestChanges invokes actionSubmissionRequestChanges operation.
@ -303,13 +294,13 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params a
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionReview
return ErrPermissionDeniedNeedRoleMapReview
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusChangesRequested)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidated, model.StatusAccepted, model.StatusSubmitted}, smap)
smap.Add("status_id", model.SubmissionStatusChangesRequested)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated, model.SubmissionStatusAccepted, model.SubmissionStatusSubmitted}, smap)
}
// ActionSubmissionRevoke invokes actionSubmissionRevoke operation.
@ -340,8 +331,8 @@ func (svc *Service) ActionSubmissionRevoke(ctx context.Context, params api.Actio
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusUnderConstruction)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted, model.StatusChangesRequested}, smap)
smap.Add("status_id", model.SubmissionStatusUnderConstruction)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted, model.SubmissionStatusChangesRequested}, smap)
}
// ActionSubmissionSubmit invokes actionSubmissionSubmit operation.
@ -372,8 +363,8 @@ func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.Actio
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusSubmitted)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction, model.StatusChangesRequested}, smap)
smap.Add("status_id", model.SubmissionStatusSubmitted)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}, smap)
}
// ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
@ -393,26 +384,19 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionUpload
}
// track who is performing the upload action
userId, err := userInfo.GetUserID()
if err != nil {
return err
return ErrPermissionDeniedNeedRoleMapUpload
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusUploading)
smap.Add("uploaded_by", userId)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, smap)
smap.Add("status_id", model.SubmissionStatusUploading)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidated}, smap)
if err != nil {
return err
}
// sentinel value because we are not using rust
if submission.TargetAssetID == 0 {
if submission.UploadedAssetID == 0 {
// this is a new map
upload_new_request := model.UploadNewRequest{
SubmissionID: submission.ID,
@ -427,22 +411,10 @@ func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params ap
return err
}
svc.Nats.Publish("maptest.submissions.uploadnew", []byte(j))
svc.Nats.Publish("maptest.submissions.upload", []byte(j))
} else {
// this is a map fix
upload_fix_request := model.UploadFixRequest{
SubmissionID: submission.ID,
ModelID: submission.ValidatedAssetID,
ModelVersion: submission.ValidatedAssetVersion,
TargetAssetID: submission.TargetAssetID,
}
j, err := json.Marshal(upload_fix_request)
if err != nil {
return err
}
svc.Nats.Publish("maptest.submissions.uploadfix", []byte(j))
// refuse to operate
return ErrUploadedAssetIDAlreadyExists
}
return nil
@ -465,7 +437,7 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionUpload
return ErrPermissionDeniedNeedRoleMapUpload
}
// check when submission was updated
@ -480,8 +452,8 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params api.Ac
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUploading}, smap)
smap.Add("status_id", model.SubmissionStatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
}
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
@ -501,7 +473,7 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionReview
return ErrPermissionDeniedNeedRoleMapReview
}
// read submission (this could be done with a transaction WHERE clause)
@ -510,38 +482,19 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
return err
}
// track who is performing the accept action
userId, err := userInfo.GetUserID()
has_role, err = userInfo.IsSubmitter(uint64(submission.Submitter))
if err != nil {
return err
}
// check if caller is NOT the submitter
if userId == uint64(submission.Submitter) {
if has_role {
return ErrAcceptOwnSubmission
}
// Check if an active submission with the same target asset id exists
if submission.TargetAssetID != 0 {
filter := datastore.Optional()
filter.Add("target_asset_id", submission.TargetAssetID)
filter.Add("status_id", ActiveAcceptedSubmissionStatuses)
active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
Number: 1,
Size: 1,
},model.SubmissionListSortDisabled)
if err != nil {
return err
}
if len(active_submissions) != 0{
return ErrActiveSubmissionSameTargetAssetID
}
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusValidating)
smap.Add("accepted_by", userId)
submission, err = svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusSubmitted}, smap)
smap.Add("status_id", model.SubmissionStatusValidating)
submission, err = svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitted}, smap)
if err != nil {
return err
}
@ -585,13 +538,13 @@ func (svc *Service) ActionSubmissionRetryValidate(ctx context.Context, params ap
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionReview
return ErrPermissionDeniedNeedRoleMapReview
}
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusValidating)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusAccepted}, smap)
smap.Add("status_id", model.SubmissionStatusValidating)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusAccepted}, smap)
if err != nil {
return err
}
@ -635,7 +588,7 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act
}
// check if caller has required role
if !has_role {
return ErrPermissionDeniedNeedRoleSubmissionReview
return ErrPermissionDeniedNeedRoleMapReview
}
// check when submission was updated
@ -650,9 +603,9 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params api.Act
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusAccepted)
smap.Add("status_id", model.SubmissionStatusAccepted)
smap.Add("status_message", "Manually forced reset")
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
}
// ReleaseSubmissions invokes releaseSubmissions operation.
@ -688,11 +641,11 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
// check each submission to make sure it is ready to release
for _,submission := range submissions{
if submission.StatusID != model.StatusUploaded{
if submission.StatusID != model.SubmissionStatusUploaded{
return ErrReleaseInvalidStatus
}
if submission.TargetAssetID == 0{
return ErrReleaseNoTargetAssetID
if submission.UploadedAssetID == 0{
return ErrReleaseNoUploadedAssetID
}
}
@ -700,7 +653,7 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
date := request[i].Date.Unix()
// create each map with go-grpc
_, err := svc.Client.Create(ctx, &maps.MapRequest{
ID: submission.TargetAssetID,
ID: submission.UploadedAssetID,
DisplayName: &submission.DisplayName,
Creator: &submission.Creator,
GameID: &submission.GameID,
@ -712,8 +665,8 @@ func (svc *Service) ReleaseSubmissions(ctx context.Context, request []api.Releas
// update each status to Released
smap := datastore.Optional()
smap.Add("status_id", model.StatusReleased)
err = svc.DB.Submissions().IfStatusThenUpdate(ctx, submission.ID, []model.Status{model.StatusUploaded}, smap)
smap.Add("status_id", model.SubmissionStatusReleased)
err = svc.DB.Submissions().IfStatusThenUpdate(ctx, submission.ID, []model.SubmissionStatus{model.SubmissionStatusUploaded}, smap)
if err != nil {
return err
}

@ -19,7 +19,8 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a
Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
Source: req.Source,
SubmissionID: req.SubmissionID.Or(0),
ResourceType: model.ResourceType(req.ResourceType),
ResourceID: req.ResourceID.Or(0),
})
if err != nil {
return nil, err
@ -69,7 +70,8 @@ func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParam
ID: item.ID,
Hash: model.HashFormat(uint64(item.Hash)),
Source: item.Source,
SubmissionID: item.SubmissionID,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
})
}
@ -92,6 +94,7 @@ func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (
Name: script.Name,
Hash: model.HashFormat(uint64(script.Hash)),
Source: script.Source,
SubmissionID: script.SubmissionID,
ResourceType: int32(script.ResourceType),
ResourceID: script.ResourceID,
}, nil
}

@ -20,7 +20,7 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i
pmap.AddNotNil("validated_asset_version", params.ValidatedModelVersion)
// DO NOT reset completed when validated model is updated
// pmap.Add("completed", false)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, pmap)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap)
}
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
@ -29,12 +29,10 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error {
println("[ActionSubmissionValidated] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
smap.Add("status_id", model.SubmissionStatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
}
// ActionSubmissionAccepted implements actionSubmissionAccepted operation.
@ -43,13 +41,11 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params intern
//
// POST /submissions/{SubmissionID}/status/validator-failed
func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params internal.ActionSubmissionAcceptedParams) error {
println("[ActionSubmissionAccepted] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusAccepted)
smap.Add("status_id", model.SubmissionStatusAccepted)
smap.Add("status_message", params.StatusMessage)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
}
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
@ -58,13 +54,9 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error {
println("[ActionSubmissionUploaded] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusUploaded)
if params.TargetAssetID.IsSet() {
smap.AddNotNil("target_asset_id", params.TargetAssetID.Value)
}
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUploading}, smap)
smap.Add("status_id", model.SubmissionStatusUploaded)
smap.Add("uploaded_asset_id", params.UploadedAssetID)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
}

@ -14,7 +14,7 @@ impl Context{
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{
let url_raw=format!("{}/scripts",self.0.base_url);
@ -33,15 +33,18 @@ impl Context{
if let Some(source)=config.Source{
query_pairs.append_pair("Source",source);
}
if let Some(submission_id)=config.SubmissionID{
query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str());
if let Some(resource_type)=config.ResourceType{
query_pairs.append_pair("ResourceType",(resource_type as i32).to_string().as_str());
}
if let Some(resource_id)=config.ResourceID{
query_pairs.append_pair("ResourceID",resource_id.to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{
@ -50,7 +53,8 @@ impl Context{
Hash:Some(config.hash),
Name:None,
Source:None,
SubmissionID:None,
ResourceType:None,
ResourceID:None,
}).await.map_err(SingleItemError::Other)?;
if 1<scripts.len(){
return Err(SingleItemError::DuplicateItems);
@ -66,7 +70,7 @@ impl Context{
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_script_policies<'a>(&self,config:GetScriptPoliciesRequest<'a>)->Result<Vec<ScriptPolicyResponse>,Error>{
let url_raw=format!("{}/script-policy",self.0.base_url);
@ -90,7 +94,7 @@ impl Context{
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_script_policy_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{
let policies=self.get_script_policies(GetScriptPoliciesRequest{
@ -114,7 +118,7 @@ impl Context{
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn update_script_policy(&self,config:UpdateScriptPolicyRequest)->Result<(),Error>{
let url_raw=format!("{}/script-policy/{}",self.0.base_url,config.ID.0);

@ -29,7 +29,7 @@ impl Context{
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{
let url_raw=format!("{}/scripts",self.0.base_url);
@ -48,15 +48,18 @@ impl Context{
if let Some(source)=config.Source{
query_pairs.append_pair("Source",source);
}
if let Some(submission_id)=config.SubmissionID{
query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str());
if let Some(resource_type)=config.ResourceType{
query_pairs.append_pair("ResourceType",(resource_type as i32).to_string().as_str());
}
if let Some(resource_id)=config.ResourceID{
query_pairs.append_pair("ResourceID",resource_id.to_string().as_str());
}
}
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{
let scripts=self.get_scripts(GetScriptsRequest{
@ -65,7 +68,8 @@ impl Context{
Hash:Some(config.hash),
Name:None,
Source:None,
SubmissionID:None,
ResourceType:None,
ResourceID:None,
}).await.map_err(SingleItemError::Other)?;
if 1<scripts.len(){
return Err(SingleItemError::DuplicateItems);
@ -81,7 +85,7 @@ impl Context{
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_script_policies<'a>(&self,config:GetScriptPoliciesRequest<'a>)->Result<Vec<ScriptPolicyResponse>,Error>{
let url_raw=format!("{}/script-policy",self.0.base_url);
@ -105,7 +109,7 @@ impl Context{
response_ok(
self.0.get(url).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn get_script_policy_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptPolicyResponse>,SingleItemError>{
let policies=self.get_script_policies(GetScriptPoliciesRequest{
@ -129,7 +133,7 @@ impl Context{
response_ok(
self.0.post(url,body).await.map_err(Error::Reqwest)?
).await.map_err(Error::Response)?
.json().await.map_err(Error::Reqwest)
.json().await.map_err(Error::ReqwestJson)
}
pub async fn update_submission_validated_model(&self,config:UpdateSubmissionModelRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/validated-model",self.0.base_url,config.SubmissionID);
@ -151,9 +155,9 @@ impl Context{
let url_raw=format!("{}/submissions/{}/status/validator-uploaded",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
if let Some(target_asset_id)=config.TargetAssetID{
{
url.query_pairs_mut()
.append_pair("TargetAssetID",target_asset_id.to_string().as_str());
.append_pair("UploadedAssetID",config.UploadedAssetID.to_string().as_str());
}
response_ok(
self.0.post_empty_body(url).await.map_err(Error::Reqwest)?

@ -2,6 +2,7 @@
pub enum Error{
Parse(url::ParseError),
Reqwest(reqwest::Error),
ReqwestJson(reqwest::Error),
Response(ResponseError),
JSON(serde_json::Error),
}
@ -65,6 +66,14 @@ pub struct ScriptID(pub(crate)i64);
#[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)]
pub struct ScriptPolicyID(pub(crate)i64);
#[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
#[repr(i32)]
pub enum ResourceType{
Unknown=0,
Mapfix=1,
Submission=2,
}
#[allow(nonstandard_style)]
pub struct GetScriptRequest{
pub ScriptID:ScriptID,
@ -81,7 +90,9 @@ pub struct GetScriptsRequest<'a>{
#[serde(skip_serializing_if="Option::is_none")]
pub Source:Option<&'a str>,
#[serde(skip_serializing_if="Option::is_none")]
pub SubmissionID:Option<i64>,
pub ResourceType:Option<ResourceType>,
#[serde(skip_serializing_if="Option::is_none")]
pub ResourceID:Option<i64>,
}
#[derive(Clone,Copy,Debug)]
pub struct HashRequest<'a>{
@ -94,15 +105,17 @@ pub struct ScriptResponse{
pub Name:String,
pub Hash:String,
pub Source:String,
pub SubmissionID:i64,
pub ResourceType:ResourceType,
pub ResourceID:i64,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Serialize)]
pub struct CreateScriptRequest<'a>{
pub Name:&'a str,
pub Source:&'a str,
pub ResourceType:ResourceType,
#[serde(skip_serializing_if="Option::is_none")]
pub SubmissionID:Option<i64>,
pub ResourceID:Option<i64>,
}
#[allow(nonstandard_style)]
#[derive(Clone,Debug,serde::Deserialize)]
@ -177,7 +190,7 @@ pub struct UpdateSubmissionModelRequest{
#[derive(Clone,Debug)]
pub struct ActionSubmissionUploadedRequest{
pub SubmissionID:i64,
pub TargetAssetID:Option<u64>,
pub UploadedAssetID:u64,
}
#[allow(nonstandard_style)]

@ -1,10 +1,12 @@
use futures::StreamExt;
mod nats_types;
mod validator;
mod upload_new;
mod upload_fix;
mod message_handler;
mod nats_types;
mod types;
mod uploader;
mod upload_submission;
mod validator;
mod validate_submission;
#[allow(dead_code)]
#[derive(Debug)]
@ -52,7 +54,7 @@ async fn main()->Result<(),StartupError>{
.get_or_create_consumer("validation",async_nats::jetstream::consumer::pull::Config{
name:Some("validation".to_owned()),
durable_name:Some("validation".to_owned()),
filter_subject:"maptest.submissions.>".to_owned(),
filter_subject:"maptest.>".to_owned(),
..Default::default()
}).await.map_err(StartupError::NatsConsumer)?
.messages().await.map_err(StartupError::NatsStream)

@ -5,9 +5,8 @@ pub enum HandleMessageError{
DoubleAck(async_nats::Error),
Json(serde_json::Error),
UnknownSubject(String),
UploadNew(crate::upload_new::UploadError),
UploadFix(crate::upload_fix::UploadError),
Validation(crate::validator::ValidateError),
UploadSubmission(crate::upload_submission::UploadError),
ValidateSubmission(crate::validate_submission::ValidateSubmissionError),
}
impl std::fmt::Display for HandleMessageError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -23,9 +22,8 @@ fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleM
}
pub struct MessageHandler{
upload_new:crate::upload_new::Uploader,
upload_fix:crate::upload_fix::Uploader,
validator:crate::validator::Validator,
upload_submission:crate::upload_submission::Uploader,
validate_submission:crate::validate_submission::Validator,
}
impl MessageHandler{
@ -35,18 +33,16 @@ impl MessageHandler{
api:submissions_api::internal::Context,
)->Self{
Self{
upload_new:crate::upload_new::Uploader::new(cookie_context.clone(),group_id,api.clone()),
upload_fix:crate::upload_fix::Uploader::new(cookie_context.clone(),group_id,api.clone()),
validator:crate::validator::Validator::new(cookie_context,api),
upload_submission:crate::upload_submission::Uploader::new(crate::uploader::Uploader::new(cookie_context.clone(),group_id,api.clone())),
validate_submission:crate::validate_submission::Validator::new(crate::validator::Validator::new(cookie_context,api)),
}
}
pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{
let message=message_result.map_err(HandleMessageError::Messages)?;
message.double_ack().await.map_err(HandleMessageError::DoubleAck)?;
match message.subject.as_str(){
"maptest.submissions.uploadnew"=>self.upload_new.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadNew),
"maptest.submissions.uploadfix"=>self.upload_fix.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadFix),
"maptest.submissions.validate"=>self.validator.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::Validation),
"maptest.submissions.upload"=>self.upload_submission.upload(from_slice(&message.payload)?).await.map_err(HandleMessageError::UploadSubmission),
"maptest.submissions.validate"=>self.validate_submission.validate(from_slice(&message.payload)?).await.map_err(HandleMessageError::ValidateSubmission),
other=>Err(HandleMessageError::UnknownSubject(other.to_owned()))
}
}

@ -6,7 +6,7 @@
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ValidateRequest{
pub struct ValidateSubmissionRequest{
// submission_id is passed back in the response message
pub SubmissionID:i64,
pub ModelID:u64,
@ -14,21 +14,22 @@ pub struct ValidateRequest{
pub ValidatedModelID:Option<u64>,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ValidateMapfixRequest{
// submission_id is passed back in the response message
pub MapfixID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
pub ValidatedModelID:Option<u64>,
}
// Create a new map
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct UploadNewRequest{
pub struct UploadSubmissionRequest{
pub SubmissionID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
pub ModelName:String,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct UploadFixRequest{
pub SubmissionID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
pub TargetAssetID:u64,
}

4
validation/src/types.rs Normal file

@ -0,0 +1,4 @@
pub enum ResourceID{
Mapfix(i64),
Submission(i64),
}

@ -1,62 +0,0 @@
use crate::nats_types::UploadFixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum UploadError{
Get(rbx_asset::cookie::GetError),
Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError),
ApiActionSubmissionUploaded(submissions_api::Error),
}
impl std::fmt::Display for UploadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for UploadError{}
pub struct Uploader{
roblox_cookie:rbx_asset::cookie::CookieContext,
group_id:Option<u64>,
api_internal:submissions_api::internal::Context,
}
impl Uploader{
pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext,
group_id:Option<u64>,
api_internal:submissions_api::internal::Context,
)->Self{
Self{
roblox_cookie,
group_id,
api_internal,
}
}
pub async fn upload(&self,upload_info:UploadFixRequest)->Result<(),UploadError>{
// download the map model version
let model_data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion),
}).await.map_err(UploadError::Get)?;
// upload the map to the strafesnet group
let _upload_response=self.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{
assetid:upload_info.TargetAssetID,
groupId:self.group_id,
name:None,
description:None,
ispublic:None,
allowComments:None,
},model_data).await.map_err(UploadError::Upload)?;
// that's it, the database entry does not need to be changed.
// mark submission as uploaded, TargetAssetID is unchanged
self.api_internal.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{
SubmissionID:upload_info.SubmissionID,
TargetAssetID:None,
}).await.map_err(UploadError::ApiActionSubmissionUploaded)?;
Ok(())
}
}

@ -0,0 +1,50 @@
use crate::nats_types::UploadMapfixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum UploadError{
Get(rbx_asset::cookie::GetError),
Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError),
ApiActionMapfixUploaded(submissions_api::Error),
}
impl std::fmt::Display for UploadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for UploadError{}
pub struct Uploader(crate::uploader::Uploader);
impl Uploader{
pub const fn new(inner:crate::uploader::Uploader)->Self{
Self(inner)
}
pub async fn upload(&self,upload_info:UploadMapfixRequest)->Result<(),UploadError>{
let Self(uploader)=self;
// download the map model version
let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion),
}).await.map_err(UploadError::Get)?;
// upload the map to the strafesnet group
let _upload_response=uploader.roblox_cookie.upload(rbx_asset::cookie::UploadRequest{
assetid:upload_info.TargetAssetID,
groupId:uploader.group_id,
name:None,
description:None,
ispublic:None,
allowComments:None,
},model_data).await.map_err(UploadError::Upload)?;
// that's it, the database entry does not need to be changed.
// mark mapfix as uploaded, TargetAssetID is unchanged
uploader.api.action_mapfix_uploaded(submissions_api::types::ActionMapfixUploadedRequest{
MapfixID:upload_info.MapfixID,
}).await.map_err(UploadError::ApiActionMapfixUploaded)?;
Ok(())
}
}

@ -1,4 +1,4 @@
use crate::nats_types::UploadNewRequest;
use crate::nats_types::UploadSubmissionRequest;
#[allow(dead_code)]
#[derive(Debug)]
@ -16,43 +16,32 @@ impl std::fmt::Display for UploadError{
}
impl std::error::Error for UploadError{}
pub struct Uploader{
roblox_cookie:rbx_asset::cookie::CookieContext,
group_id:Option<u64>,
api:submissions_api::internal::Context,
}
pub struct Uploader(crate::uploader::Uploader);
impl Uploader{
pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext,
group_id:Option<u64>,
api:submissions_api::internal::Context,
)->Self{
Self{
roblox_cookie,
group_id,
api,
}
pub const fn new(inner:crate::uploader::Uploader)->Self{
Self(inner)
}
pub async fn upload(&self,upload_info:UploadNewRequest)->Result<(),UploadError>{
pub async fn upload(&self,upload_info:UploadSubmissionRequest)->Result<(),UploadError>{
let Self(uploader)=self;
// download the map model version
let model_data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
let model_data=uploader.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:upload_info.ModelID,
version:Some(upload_info.ModelVersion),
}).await.map_err(UploadError::Get)?;
// upload the map to the strafesnet group
let upload_response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
let upload_response=uploader.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
name:upload_info.ModelName.clone(),
description:"".to_owned(),
ispublic:false,
allowComments:false,
groupId:self.group_id,
groupId:uploader.group_id,
},model_data).await.map_err(UploadError::Create)?;
// note the asset id of the created model for later release, and mark the submission as uploaded
self.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{
uploader.api.action_submission_uploaded(submissions_api::types::ActionSubmissionUploadedRequest{
SubmissionID:upload_info.SubmissionID,
TargetAssetID:Some(upload_response.AssetId),
UploadedAssetID:upload_response.AssetId,
}).await.map_err(UploadError::ApiActionSubmissionUploaded)?;
Ok(())

@ -0,0 +1,18 @@
pub struct Uploader{
pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext,
pub(crate) group_id:Option<u64>,
pub(crate) api:submissions_api::internal::Context,
}
impl Uploader{
pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext,
group_id:Option<u64>,
api:submissions_api::internal::Context,
)->Self{
Self{
roblox_cookie,
group_id,
api,
}
}
}

@ -0,0 +1,45 @@
use crate::nats_types::ValidateMapfixRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum ValidateMapfixError{
ApiActionMapfixValidate(submissions_api::Error),
}
impl std::fmt::Display for ValidateMapfixError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ValidateMapfixError{}
pub struct Validator(crate::validator::Validator);
impl Validator{
pub const fn new(inner:crate::validator::Validator)->Self{
Self(inner)
}
pub async fn validate(&self,validate_info:ValidateMapfixRequest)->Result<(),ValidateMapfixError>{
let Self(validator)=self;
let mapfix_id=validate_info.MapfixID;
let validate_result=validator.validate(validate_info.into()).await;
// update the mapfix depending on the result
match &validate_result{
Ok(())=>{
// update the mapfix model status to validated
validator.api.action_mapfix_validated(
submissions_api::types::MapfixID(mapfix_id)
).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?;
},
Err(e)=>{
// update the mapfix model status to accepted
validator.api.action_mapfix_accepted(submissions_api::types::ActionMapfixAcceptedRequest{
MapfixID:mapfix_id,
StatusMessage:format!("{e}"),
}).await.map_err(ValidateMapfixError::ApiActionMapfixValidate)?;
},
}
Ok(())
}
}

@ -0,0 +1,45 @@
use crate::nats_types::ValidateSubmissionRequest;
#[allow(dead_code)]
#[derive(Debug)]
pub enum ValidateSubmissionError{
ApiActionSubmissionValidate(submissions_api::Error),
}
impl std::fmt::Display for ValidateSubmissionError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ValidateSubmissionError{}
pub struct Validator(crate::validator::Validator);
impl Validator{
pub const fn new(inner:crate::validator::Validator)->Self{
Self(inner)
}
pub async fn validate(&self,validate_info:ValidateSubmissionRequest)->Result<(),ValidateSubmissionError>{
let Self(validator)=self;
let submission_id=validate_info.SubmissionID;
let validate_result=validator.validate(validate_info.into()).await;
// update the submission depending on the result
match &validate_result{
Ok(())=>{
// update the submission model status to validated
validator.api.action_submission_validated(
submissions_api::types::SubmissionID(submission_id)
).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?;
},
Err(e)=>{
// update the submission model status to accepted
validator.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{
SubmissionID:submission_id,
StatusMessage:format!("{e}"),
}).await.map_err(ValidateSubmissionError::ApiActionSubmissionValidate)?;
},
}
Ok(())
}
}

@ -1,6 +1,7 @@
use futures::TryStreamExt;
use submissions_api::types::ResourceType;
use crate::nats_types::ValidateRequest;
use crate::types::ResourceID;
const SCRIPT_CONCURRENCY:usize=16;
@ -41,8 +42,8 @@ pub enum ValidateError{
ApiCreateScript(submissions_api::Error),
ApiCreateScriptPolicy(submissions_api::Error),
ApiGetScriptFromHash(submissions_api::types::SingleItemError),
ApiUpdateMapfixModelUnimplemented,
ApiUpdateSubmissionModel(submissions_api::Error),
ApiActionSubmissionValidate(submissions_api::Error),
ModelFileRootMustHaveOneChild,
ModelFileChildRefIsNil,
ModelFileEncode(rbx_binary::EncodeError),
@ -56,9 +57,38 @@ impl std::fmt::Display for ValidateError{
}
impl std::error::Error for ValidateError{}
#[allow(nonstandard_style)]
pub struct ValidateRequest{
pub ModelID:u64,
pub ModelVersion:u64,
pub ValidatedModelID:Option<u64>,
pub ResourceID:ResourceID,
}
impl From<crate::nats_types::ValidateMapfixRequest> for ValidateRequest{
fn from(value:crate::nats_types::ValidateMapfixRequest)->Self{
Self{
ModelID:value.ModelID,
ModelVersion:value.ModelVersion,
ValidatedModelID:value.ValidatedModelID,
ResourceID:ResourceID::Mapfix(value.MapfixID),
}
}
}
impl From<crate::nats_types::ValidateSubmissionRequest> for ValidateRequest{
fn from(value:crate::nats_types::ValidateSubmissionRequest)->Self{
Self{
ModelID:value.ModelID,
ModelVersion:value.ModelVersion,
ValidatedModelID:value.ValidatedModelID,
ResourceID:ResourceID::Submission(value.SubmissionID),
}
}
}
pub struct Validator{
roblox_cookie:rbx_asset::cookie::CookieContext,
api:submissions_api::internal::Context,
pub(crate) roblox_cookie:rbx_asset::cookie::CookieContext,
pub(crate) api:submissions_api::internal::Context,
}
impl Validator{
@ -72,29 +102,6 @@ impl Validator{
}
}
pub async fn validate(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{
let submission_id=validate_info.SubmissionID;
let validate_result=self.validate_inner(validate_info).await;
// update the submission depending on the result
match &validate_result{
Ok(())=>{
// update the submission model status to validated
self.api.action_submission_validated(
submissions_api::types::SubmissionID(submission_id)
).await.map_err(ValidateError::ApiActionSubmissionValidate)?;
},
Err(e)=>{
// update the submission model status to accepted
self.api.action_submission_accepted(submissions_api::types::ActionSubmissionAcceptedRequest{
SubmissionID:submission_id,
StatusMessage:format!("{e}"),
}).await.map_err(ValidateError::ApiActionSubmissionValidate)?;
},
}
validate_result
}
pub async fn validate_inner(&self,validate_info:ValidateRequest)->Result<(),ValidateError>{
// download map
let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id:validate_info.ModelID,
@ -155,11 +162,17 @@ impl Validator{
},
};
}else{
let (resource_type,resource_id)=match validate_info.ResourceID{
ResourceID::Mapfix(mapfix_id)=>(ResourceType::Mapfix,mapfix_id),
ResourceID::Submission(submission_id)=>(ResourceType::Submission,submission_id),
};
// upload the script
let script=self.api.create_script(submissions_api::types::CreateScriptRequest{
Name:name.as_str(),
Source:source.as_str(),
SubmissionID:Some(validate_info.SubmissionID),
ResourceType:resource_type,
ResourceID:Some(resource_id),
}).await.map_err(ValidateError::ApiCreateScript)?;
// create a None policy (pending review by yours truly)
@ -252,13 +265,21 @@ impl Validator{
response.AssetId
};
// update the submission to use the validated model
self.api.update_submission_validated_model(submissions_api::types::UpdateSubmissionModelRequest{
SubmissionID:validate_info.SubmissionID,
ModelID:model_id,
ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateSubmissionModel)?;
};
match validate_info.ResourceID{
ResourceID::Mapfix(_mapfix_id)=>{
// update the mapfix to use the validated model
return Err(ValidateError::ApiUpdateMapfixModelUnimplemented);
},
ResourceID::Submission(submission_id)=>{
// update the submission to use the validated model
self.api.update_submission_validated_model(submissions_api::types::UpdateSubmissionModelRequest{
SubmissionID:submission_id,
ModelID:model_id,
ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateSubmissionModel)?;
},
}
}
Ok(())
}

@ -1,6 +1,6 @@
"use client"
import { FormControl, FormControlLabel, Button, TextField, Checkbox } from "@mui/material"
import { Button, TextField } from "@mui/material"
import GameSelection from "./_game";
import SendIcon from '@mui/icons-material/Send';
@ -15,7 +15,6 @@ interface SubmissionPayload {
GameID: number;
AssetID: number;
AssetVersion: number;
TargetAssetID: number;
}
interface IdResponse {
ID: number;
@ -23,7 +22,6 @@ interface IdResponse {
export default function SubmissionInfoPage() {
const [game, setGame] = useState(1);
const [isFixingMap, setIsFixingMap] = useState(false);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@ -37,7 +35,6 @@ export default function SubmissionInfoPage() {
GameID: game,
AssetID: Number((formData.get("asset-id") as string) ?? "0"),
AssetVersion: 0,
TargetAssetID: isFixingMap ? Number((formData.get("target-asset-id") as string) ?? "0") : 0,
};
console.log(payload)
@ -84,15 +81,6 @@ export default function SubmissionInfoPage() {
<TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID" variant="outlined"/>
{/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */}
<GameSelection game={game} setGame={setGame} />
<FormControl>
<FormControlLabel control={<Checkbox sx={{
color: "#646464",
'&.Mui-checked': {
color: "#66BB6A",
},
}} onChange={(e) => setIsFixingMap(e.target.checked)} />} label="Fixing an Existing Map?" />
</FormControl>
<TextField className="form-field" id="target-asset-id" name="target-asset-id" label="Target Asset ID (group model)" variant="outlined"/>
<span className="spacer form-spacer"></span>
<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
width: "400px",