Scream Test Backend Overhaul #237

Merged
Quaternions merged 5 commits from staging into master 2025-07-18 06:28:19 +00:00
29 changed files with 3684 additions and 3041 deletions

View File

@@ -6,6 +6,12 @@ info:
tags:
- name: Mapfixes
description: Mapfix operations
- name: Operations
description: Long-running operations
- name: Scripts
description: Script operations
- name: ScriptPolicy
description: Script policy operations
- name: Submissions
description: Submission operations
paths:
@@ -539,7 +545,7 @@ paths:
summary: Get list of scripts
operationId: listScripts
tags:
- Script
- Scripts
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/Limit"

View File

@@ -11,6 +11,7 @@ import (
"git.itzana.me/strafesnet/maps-service/pkg/datastore/gormstore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"git.itzana.me/strafesnet/maps-service/pkg/web_api"
"git.itzana.me/strafesnet/maps-service/pkg/service"
"git.itzana.me/strafesnet/maps-service/pkg/service_internal"
"github.com/nats-io/nats.go"
@@ -130,36 +131,37 @@ func serve(ctx *cli.Context) error {
if err != nil {
log.Fatal(err)
}
svc := &service.Service{
DB: db,
Nats: js,
Maps: maps.NewMapsServiceClient(conn),
Users: users.NewUsersServiceClient(conn),
Roblox: roblox.Client{
svc_inner := service.NewService(
db,
js,
maps.NewMapsServiceClient(conn),
users.NewUsersServiceClient(conn),
)
svc_external := web_api.NewService(
&svc_inner,
roblox.Client{
HttpClient: http.DefaultClient,
ApiKey: ctx.String("rbx-api-key"),
},
}
)
conn, err = grpc.Dial(ctx.String("auth-rpc-host"), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
sec := service.SecurityHandler{
sec := web_api.SecurityHandler{
Client: auth.NewAuthServiceClient(conn),
}
srv, err := api.NewServer(svc, sec, api.WithPathPrefix("/v1"))
srv_external, err := api.NewServer(&svc_external, sec, api.WithPathPrefix("/v1"))
if err != nil {
log.WithError(err).Fatal("failed to initialize api server")
}
svc2 := &service_internal.Service{
DB: db,
Nats: js,
}
svc_internal := service_internal.NewService(&svc_inner)
srv2, err := internal.NewServer(svc2, internal.WithPathPrefix("/v1"))
srv_internal, err := internal.NewServer(&svc_internal, internal.WithPathPrefix("/v1"))
if err != nil {
log.WithError(err).Fatal("failed to initialize api server")
}
@@ -168,12 +170,12 @@ func serve(ctx *cli.Context) error {
// First server
go func(errChan chan error) {
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port-internal")), srv2)
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port-internal")), srv_internal)
}(errChan)
// Second server
go func(errChan chan error) {
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv)
errChan <- http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv_external)
}(errChan)
// Wait for the first error or completion of both tasks

33
pkg/model/roles.go Normal file
View File

@@ -0,0 +1,33 @@
package model
// Submissions roles bitflag
type Roles int32
var (
RolesSubmissionUpload Roles = 1<<6
RolesSubmissionReview Roles = 1<<5
RolesSubmissionRelease Roles = 1<<4
RolesScriptWrite Roles = 1<<3
RolesMapfixUpload Roles = 1<<2
RolesMapfixReview Roles = 1<<1
RolesMapDownload Roles = 1<<0
RolesEmpty Roles = 0
)
// StrafesNET group roles
type GroupRole int32
var (
// has ScriptWrite
RoleQuat GroupRole = 255
RoleItzaname GroupRole = 254
RoleStagingDeveloper GroupRole = 240
RolesAll Roles = ^RolesEmpty
// has SubmissionUpload
RoleMapAdmin GroupRole = 128
RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesMapCouncil
// has MapfixReview
RoleMapCouncil GroupRole = 64
RolesMapCouncil Roles = RolesMapfixReview|RolesMapfixUpload|RolesMapAccess
// access to downloading maps
RoleMapAccess GroupRole = 32
RolesMapAccess Roles = RolesMapDownload
)

View File

@@ -16,7 +16,7 @@ func (svc *Service) ListAuditEvents(ctx context.Context, resource model.Resource
filter.Add("resource_type", resource.Type)
filter.Add("resource_id", resource.ID)
items, err := svc.DB.AuditEvents().List(ctx, filter, page)
items, err := svc.db.AuditEvents().List(ctx, filter, page)
if err != nil {
return nil, err
}
@@ -32,7 +32,7 @@ func (svc *Service) ListAuditEvents(ctx context.Context, resource model.Resource
idList.ID = append(idList.ID, userId)
}
userList, err := svc.Users.GetList(ctx, &idList)
userList, err := svc.users.GetList(ctx, &idList)
if err != nil {
return nil, err
}
@@ -74,7 +74,7 @@ func (svc *Service) CreateAuditEventAction(ctx context.Context, userId uint64, r
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
@@ -95,7 +95,7 @@ func (svc *Service) CreateAuditEventComment(ctx context.Context, userId uint64,
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
@@ -116,7 +116,7 @@ func (svc *Service) CreateAuditEventChangeModel(ctx context.Context, userId uint
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
@@ -130,3 +130,67 @@ func (svc *Service) CreateAuditEventChangeModel(ctx context.Context, userId uint
return nil
}
func (svc *Service) CreateAuditEventChangeValidatedModel(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeValidatedModel) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeChangeValidatedModel,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
func (svc *Service) CreateAuditEventError(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataError) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeError,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
func (svc *Service) CreateAuditEventCheckList(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataCheckList) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.db.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeCheckList,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,114 +2,115 @@ package service
import (
"context"
"strings"
"time"
"git.itzana.me/strafesnet/go-grpc/maps"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// ListMaps implements listMaps operation.
//
// Get list of maps.
//
// GET /maps
func (svc *Service) ListMaps(ctx context.Context, params api.ListMapsParams) ([]api.Map, error) {
filter := maps.MapFilter{}
// Optional map used to update an object
type MapUpdate maps.MapRequest
if params.DisplayName.IsSet(){
filter.DisplayName = &params.DisplayName.Value
}
if params.Creator.IsSet(){
filter.Creator = &params.Creator.Value
}
if params.GameID.IsSet(){
filter.GameID = &params.GameID.Value
func NewMapUpdate(id int64) *MapUpdate {
item := MapUpdate{
ID: id,
}
return &item
}
func (update *MapUpdate) SetDisplayName(display_name string) {
update.DisplayName = &display_name
}
func (update *MapUpdate) SetCreator(creator string) {
update.Creator = &creator
}
func (update *MapUpdate) SetGameID(game_id int32) {
update.GameID = &game_id
}
func (update *MapUpdate) SetDate(date int64) {
update.Date = &date
}
mapList, err := svc.Maps.List(ctx, &maps.ListRequest{
Filter: &filter,
Page: &maps.Pagination{
Size: params.Limit,
Number: params.Page,
},
// Optional map used to find matching objects
type MapFilter maps.MapFilter
func NewMapFilter() *MapFilter {
item := MapFilter{}
return &item
}
func (filter *MapFilter) SetDisplayName(display_name string) {
filter.DisplayName = &display_name
}
func (filter *MapFilter) SetCreator(creator string) {
filter.Creator = &creator
}
func (filter *MapFilter) SetGameID(game_id int32) {
filter.GameID = &game_id
}
func (svc *Service) CreateMap(ctx context.Context, item model.Map) (int64, error) {
date := item.Date.Unix()
id_message, err := svc.maps.Create(ctx, &maps.MapRequest{
ID: item.ID,
DisplayName: &item.DisplayName,
Creator: &item.Creator,
GameID: &item.GameID,
Date: &date,
})
if err != nil {
return 0, err
}
return id_message.ID, nil
}
func (svc *Service) ListMaps(ctx context.Context, filter *MapFilter, page model.Page) ([]model.Map, error) {
page_request := maps.Pagination{
Size: page.Size,
Number: page.Number,
}
request := maps.ListRequest{
Filter: (*maps.MapFilter)(filter),
Page: &page_request,
}
items, err := svc.maps.List(ctx, &request)
if err != nil {
return nil, err
}
var resp []api.Map
for _, item := range mapList.Maps {
resp = append(resp, api.Map{
ID: item.ID,
DisplayName: item.DisplayName,
Creator: item.Creator,
GameID: item.GameID,
Date: item.Date,
})
resp := make([]model.Map, len(items.Maps))
for i, item := range items.Maps {
resp[i].ID = item.ID
resp[i].DisplayName = item.DisplayName
resp[i].Creator = item.Creator
resp[i].GameID = item.GameID
resp[i].Date = time.Unix(item.Date, 0)
}
return resp, nil
}
// GetMap implements getScript operation.
//
// Get the specified script by ID.
//
// GET /maps/{MapID}
func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.Map, error) {
mapResponse, err := svc.Maps.Get(ctx, &maps.IdMessage{
ID: params.MapID,
})
func (svc *Service) DeleteMap(ctx context.Context, id int64) error {
_, err := svc.maps.Delete(ctx, &maps.IdMessage{ID: id})
return err
}
func (svc *Service) GetMap(ctx context.Context, id int64) (*model.Map, error) {
item, err := svc.maps.Get(ctx, &maps.IdMessage{ID: id})
if err != nil {
return nil, err
}
return &api.Map{
ID: mapResponse.ID,
DisplayName: mapResponse.DisplayName,
Creator: mapResponse.Creator,
GameID: mapResponse.GameID,
Date: mapResponse.Date,
return &model.Map{
ID: item.ID,
DisplayName: item.DisplayName,
Creator: item.Creator,
GameID: item.GameID,
Date: time.Unix(item.Date, 0),
}, nil
}
// GetMapAssetLocation invokes getMapAssetLocation operation.
//
// Get location of map asset.
//
// GET /maps/{MapID}/location
func (svc *Service) GetMapAssetLocation(ctx context.Context, params api.GetMapAssetLocationParams) (ok api.GetMapAssetLocationOK, err error) {
userInfo, success := ctx.Value("UserInfo").(UserInfoHandle)
if !success {
return ok, ErrUserInfo
}
has_role, err := userInfo.HasRoleMapDownload()
if err != nil {
return ok, err
}
if !has_role {
return ok, ErrPermissionDeniedNeedRoleMapDownload
}
// Ensure map exists in the db!
// This could otherwise be used to access any asset
_, err = svc.Maps.Get(ctx, &maps.IdMessage{
ID: params.MapID,
})
if err != nil {
return ok, err
}
info, err := svc.Roblox.GetAssetLocation(roblox.GetAssetLatestRequest{
AssetID: uint64(params.MapID),
})
if err != nil{
return ok, err
}
ok.Data = strings.NewReader(info.Location)
return ok, nil
func (svc *Service) UpdateMap(ctx context.Context, pmap *MapUpdate) error {
_, err := svc.maps.Update(ctx, (*maps.MapRequest)(pmap))
return err
}

114
pkg/service/nats_mapfix.go Normal file
View File

@@ -0,0 +1,114 @@
package service
import (
"encoding/json"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
func (svc *Service) NatsCreateMapfix(
OperationID int32,
ModelID uint64,
TargetAssetID uint64,
Description string,
) error {
create_request := model.CreateMapfixRequest{
OperationID: OperationID,
ModelID: ModelID,
TargetAssetID: TargetAssetID,
Description: Description,
}
j, err := json.Marshal(create_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.mapfixes.create", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsCheckMapfix(
MapfixID int64,
ModelID uint64,
SkipChecks bool,
) error {
validate_request := model.CheckMapfixRequest{
MapfixID: MapfixID,
ModelID: ModelID,
SkipChecks: SkipChecks,
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.mapfixes.check", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsUploadMapfix(
MapfixID int64,
ModelID uint64,
ModelVersion uint64,
TargetAssetID uint64,
) error {
upload_fix_request := model.UploadMapfixRequest{
MapfixID: MapfixID,
ModelID: ModelID,
ModelVersion: ModelVersion,
TargetAssetID: TargetAssetID,
}
j, err := json.Marshal(upload_fix_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.mapfixes.upload", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsValidateMapfix(
MapfixID int64,
ModelID uint64,
ModelVersion uint64,
ValidatedAssetID uint64,
) error {
validate_request := model.ValidateMapfixRequest{
MapfixID: MapfixID,
ModelID: ModelID,
ModelVersion: ModelVersion,
ValidatedModelID: nil,
}
// sentinel values because we're not using rust
if ValidatedAssetID != 0 {
validate_request.ValidatedModelID = &ValidatedAssetID
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.mapfixes.validate", []byte(j))
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,120 @@
package service
import (
"encoding/json"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
func (svc *Service) NatsCreateSubmission(
OperationID int32,
ModelID uint64,
DisplayName string,
Creator string,
GameID uint32,
Status uint32,
Roles uint32,
) error {
create_request := model.CreateSubmissionRequest{
OperationID: OperationID,
ModelID: ModelID,
DisplayName: DisplayName,
Creator: Creator,
GameID: GameID,
Status: Status,
Roles: Roles,
}
j, err := json.Marshal(create_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.submissions.create", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsCheckSubmission(
SubmissionID int64,
ModelID uint64,
SkipChecks bool,
) error {
validate_request := model.CheckSubmissionRequest{
SubmissionID: SubmissionID,
ModelID: ModelID,
SkipChecks: SkipChecks,
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.submissions.check", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsUploadSubmission(
SubmissionID int64,
ModelID uint64,
ModelVersion uint64,
ModelName string,
) error {
upload_new_request := model.UploadSubmissionRequest{
SubmissionID: SubmissionID,
ModelID: ModelID,
ModelVersion: ModelVersion,
ModelName: ModelName,
}
j, err := json.Marshal(upload_new_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.submissions.upload", []byte(j))
if err != nil {
return err
}
return nil
}
func (svc *Service) NatsValidateSubmission(
SubmissionID int64,
ModelID uint64,
ModelVersion uint64,
ValidatedModelID uint64,
) error {
validate_request := model.ValidateSubmissionRequest{
SubmissionID: SubmissionID,
ModelID: ModelID,
ModelVersion: ModelVersion,
ValidatedModelID: nil,
}
// sentinel values because we're not using rust
if ValidatedModelID != 0 {
validate_request.ValidatedModelID = &ValidatedModelID
}
j, err := json.Marshal(validate_request)
if err != nil {
return err
}
_, err = svc.nats.Publish("maptest.submissions.validate", []byte(j))
if err != nil {
return err
}
return nil
}

View File

@@ -2,45 +2,54 @@ package service
import (
"context"
"time"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// GetOperation implements getOperation operation.
//
// Get the specified operation by ID.
//
// GET /operations/{OperationID}
func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationParams) (*api.Operation, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
type OperationFailParams datastore.OptionalMap
// You must be the operation owner to read it
operation, err := svc.DB.Operations().Get(ctx, params.OperationID)
if err != nil {
return nil, err
}
userId, err := userInfo.GetUserID()
if err != nil {
return nil, err
}
// check if caller is the submitter
has_role := userId == operation.Owner
if !has_role {
return nil, ErrPermissionDeniedNotSubmitter
}
return &api.Operation{
OperationID: operation.ID,
Date: operation.CreatedAt.Unix(),
Owner: int64(operation.Owner),
Status: int32(operation.StatusID),
StatusMessage: operation.StatusMessage,
Path: operation.Path,
}, nil
func NewOperationFailParams(
status_message string,
) OperationFailParams {
filter := datastore.Optional()
filter.Add("status_id", model.OperationStatusFailed)
filter.Add("status_message", status_message)
return OperationFailParams(filter)
}
type OperationCompleteParams datastore.OptionalMap
func NewOperationCompleteParams(
path string,
) OperationCompleteParams {
filter := datastore.Optional()
filter.Add("status_id", model.OperationStatusCompleted)
filter.Add("path", path)
return OperationCompleteParams(filter)
}
func (svc *Service) CreateOperation(ctx context.Context, operation model.Operation) (model.Operation, error) {
return svc.db.Operations().Create(ctx, operation)
}
func (svc *Service) CountOperationsSince(ctx context.Context, owner int64, since time.Time) (int64, error) {
return svc.db.Operations().CountSince(ctx, owner, since)
}
func (svc *Service) DeleteOperation(ctx context.Context, id int32) error {
return svc.db.Operations().Delete(ctx, id)
}
func (svc *Service) GetOperation(ctx context.Context, id int32) (model.Operation, error) {
return svc.db.Operations().Get(ctx, id)
}
func (svc *Service) FailOperation(ctx context.Context, id int32, params OperationFailParams) error {
return svc.db.Operations().Update(ctx, id, datastore.OptionalMap(params))
}
func (svc *Service) CompleteOperation(ctx context.Context, id int32, params OperationCompleteParams) error {
return svc.db.Operations().Update(ctx, id, datastore.OptionalMap(params))
}

View File

@@ -3,169 +3,43 @@ package service
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// CreateScriptPolicy implements createScriptPolicy operation.
//
// Create a new script policy.
//
// POST /script-policy
func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ScriptPolicyID, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
type ScriptPolicyFilter datastore.OptionalMap
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return nil, err
}
if !has_role {
return nil, ErrPermissionDenied
}
from_script, err := svc.DB.Scripts().Get(ctx, req.FromScriptID)
if err != nil {
return nil, err
}
// the existence of ToScriptID does not need to be validated because it's checked by a foreign key constraint.
script, err := svc.DB.ScriptPolicy().Create(ctx, model.ScriptPolicy{
ID: 0,
FromScriptHash: from_script.Hash,
ToScriptID: req.ToScriptID,
Policy: model.Policy(req.Policy),
})
if err != nil {
return nil, err
}
return &api.ScriptPolicyID{
ScriptPolicyID: script.ID,
}, nil
}
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptPolicyParams) ([]api.ScriptPolicy, error) {
func NewScriptPolicyFilter() ScriptPolicyFilter {
filter := datastore.Optional()
if params.FromScriptHash.IsSet(){
hash, err := model.HashParse(params.FromScriptHash.Value)
if err != nil {
return nil, err
}
filter.Add("from_script_hash", int64(hash)) // No type safety!
}
if params.ToScriptID.IsSet(){
filter.Add("to_script_id", params.ToScriptID.Value)
}
if params.Policy.IsSet(){
filter.Add("policy", params.Policy.Value)
}
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.ScriptPolicy
for _, item := range items {
resp = append(resp, api.ScriptPolicy{
ID: item.ID,
FromScriptHash: model.HashFormat(uint64(item.FromScriptHash)),
ToScriptID: item.ToScriptID,
Policy: int32(item.Policy),
})
}
return resp, nil
return ScriptPolicyFilter(filter)
}
func (filter ScriptPolicyFilter) SetFromScriptHash(from_script_hash int64) {
// Finally, type safety!
datastore.OptionalMap(filter).Add("from_script_hash", from_script_hash)
}
func (filter ScriptPolicyFilter) SetToScriptID(to_script_id int64) {
datastore.OptionalMap(filter).Add("to_script_id", to_script_id)
}
func (filter ScriptPolicyFilter) SetPolicy(policy int32) {
datastore.OptionalMap(filter).Add("policy", policy)
}
// DeleteScriptPolicy implements deleteScriptPolicy operation.
//
// Delete the specified script policy by ID.
//
// DELETE /script-policy/{ScriptPolicyID}
func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScriptPolicyParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied
}
return svc.DB.ScriptPolicy().Delete(ctx, params.ScriptPolicyID)
func (svc *Service) CreateScriptPolicy(ctx context.Context, script model.ScriptPolicy) (model.ScriptPolicy, error) {
return svc.db.ScriptPolicy().Create(ctx, script)
}
// GetScriptPolicy implements getScriptPolicy operation.
//
// Get the specified script policy by ID.
//
// GET /script-policy/{ScriptPolicyID}
func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPolicyParams) (*api.ScriptPolicy, error) {
policy, err := svc.DB.ScriptPolicy().Get(ctx, params.ScriptPolicyID)
if err != nil {
return nil, err
}
return &api.ScriptPolicy{
ID: policy.ID,
FromScriptHash: model.HashFormat(uint64(policy.FromScriptHash)),
ToScriptID: policy.ToScriptID,
Policy: int32(policy.Policy),
}, nil
func (svc *Service) ListScriptPolicies(ctx context.Context, filter ScriptPolicyFilter, page model.Page) ([]model.ScriptPolicy, error) {
return svc.db.ScriptPolicy().List(ctx, datastore.OptionalMap(filter), page)
}
// UpdateScriptPolicy implements updateScriptPolicy operation.
//
// Update the specified script policy by ID.
//
// POST /script-policy/{ScriptPolicyID}
func (svc *Service) UpdateScriptPolicy(ctx context.Context, req *api.ScriptPolicyUpdate, params api.UpdateScriptPolicyParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied
}
pmap := datastore.Optional()
if from_script_id, ok := req.FromScriptID.Get(); ok {
from_script, err := svc.DB.Scripts().Get(ctx, from_script_id)
if err != nil {
return err
}
pmap.Add("from_script_hash", from_script.Hash)
}
if to_script_id, ok := req.ToScriptID.Get(); ok {
pmap.Add("to_script_id", to_script_id)
}
if policy, ok := req.Policy.Get(); ok {
pmap.Add("policy", policy)
}
return svc.DB.ScriptPolicy().Update(ctx, req.ID, pmap)
func (svc *Service) DeleteScriptPolicy(ctx context.Context, id int64) error {
return svc.db.ScriptPolicy().Delete(ctx, id)
}
func (svc *Service) GetScriptPolicy(ctx context.Context, id int64) (model.ScriptPolicy, error) {
return svc.db.ScriptPolicy().Get(ctx, id)
}
func (svc *Service) UpdateScriptPolicy(ctx context.Context, id int64, pmap ScriptPolicyFilter) error {
return svc.db.ScriptPolicy().Update(ctx, id, datastore.OptionalMap(pmap))
}

View File

@@ -3,173 +3,48 @@ package service
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// CreateScript implements createScript operation.
//
// Create a new script.
//
// POST /scripts
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ScriptID, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
type ScriptFilter datastore.OptionalMap
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return nil, err
}
if !has_role {
return nil, ErrPermissionDenied
}
script, err := svc.DB.Scripts().Create(ctx, model.Script{
ID: 0,
Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
Source: req.Source,
ResourceType: model.ResourceType(req.ResourceType),
ResourceID: req.ResourceID.Or(0),
})
if err != nil {
return nil, err
}
return &api.ScriptID{
ScriptID: script.ID,
}, nil
}
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParams) ([]api.Script, error) {
func NewScriptFilter() ScriptFilter {
filter := datastore.Optional()
if params.Hash.IsSet(){
hash, err := model.HashParse(params.Hash.Value)
if err != nil {
return nil, err
}
filter.Add("hash", int64(hash)) // No type safety!
}
if params.Name.IsSet(){
filter.Add("name", params.Name.Value)
}
if params.Source.IsSet(){
filter.Add("source", params.Source.Value)
}
if params.ResourceType.IsSet(){
filter.Add("resource_type", params.ResourceType.Value)
}
if params.ResourceID.IsSet(){
filter.Add("resource_id", params.ResourceID.Value)
}
items, err := svc.DB.Scripts().List(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.Script
for _, item := range items {
resp = append(resp, api.Script{
ID: item.ID,
Name: item.Name,
Hash: model.HashFormat(uint64(item.Hash)),
Source: item.Source,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
})
}
return resp, nil
return ScriptFilter(filter)
}
func (filter ScriptFilter) SetName(name string) {
datastore.OptionalMap(filter).Add("name", name)
}
func (filter ScriptFilter) SetSource(source string) {
datastore.OptionalMap(filter).Add("source", source)
}
func (filter ScriptFilter) SetHash(hash int64) {
datastore.OptionalMap(filter).Add("hash", hash)
}
func (filter ScriptFilter) SetResourceType(resource_type int32) {
datastore.OptionalMap(filter).Add("resource_type", resource_type)
}
func (filter ScriptFilter) SetResourceID(resource_id int64) {
datastore.OptionalMap(filter).Add("resource_id", resource_id)
}
// DeleteScript implements deleteScript operation.
//
// Delete the specified script by ID.
//
// DELETE /scripts/{ScriptID}
func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied
}
return svc.DB.Scripts().Delete(ctx, params.ScriptID)
func (svc *Service) CreateScript(ctx context.Context, script model.Script) (model.Script, error) {
return svc.db.Scripts().Create(ctx, script)
}
// GetScript implements getScript operation.
//
// Get the specified script by ID.
//
// GET /scripts/{ScriptID}
func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (*api.Script, error) {
script, err := svc.DB.Scripts().Get(ctx, params.ScriptID)
if err != nil {
return nil, err
}
return &api.Script{
ID: script.ID,
Name: script.Name,
Hash: model.HashFormat(uint64(script.Hash)),
Source: script.Source,
ResourceType: int32(script.ResourceType),
ResourceID: script.ResourceID,
}, nil
func (svc *Service) ListScripts(ctx context.Context, filter ScriptFilter, page model.Page) ([]model.Script, error) {
return svc.db.Scripts().List(ctx, datastore.OptionalMap(filter), page)
}
// UpdateScript implements updateScript operation.
//
// Update the specified script by ID.
//
// PATCH /scripts/{ScriptID}
func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, params api.UpdateScriptParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDenied
}
pmap := datastore.Optional()
if Name, ok := req.Name.Get(); ok {
pmap.Add("name", Name)
}
if source, ok := req.Source.Get(); ok {
pmap.Add("source", source)
pmap.Add("hash", int64(model.HashSource(source))) // No type safety!
}
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)
func (svc *Service) DeleteScript(ctx context.Context, id int64) error {
return svc.db.Scripts().Delete(ctx, id)
}
func (svc *Service) GetScript(ctx context.Context, id int64) (model.Script, error) {
return svc.db.Scripts().Get(ctx, id)
}
func (svc *Service) UpdateScript(ctx context.Context, id int64, pmap ScriptFilter) error {
return svc.db.Scripts().Update(ctx, id, datastore.OptionalMap(pmap))
}

View File

@@ -1,63 +1,29 @@
package service
import (
"context"
"errors"
"fmt"
"git.itzana.me/strafesnet/go-grpc/maps"
"git.itzana.me/strafesnet/go-grpc/users"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"github.com/nats-io/nats.go"
)
var (
// ErrPermissionDenied caller does not have the required role
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)
ErrPermissionDeniedNeedRoleMapfixUpload = fmt.Errorf("%w: Need Role MapfixUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapfixReview = fmt.Errorf("%w: Need Role MapfixReview", 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)
ErrNegativeID = errors.New("A negative ID was provided")
)
type Service struct {
DB datastore.Datastore
Nats nats.JetStreamContext
Maps maps.MapsServiceClient
Users users.UsersServiceClient
Roblox roblox.Client
db datastore.Datastore
nats nats.JetStreamContext
maps maps.MapsServiceClient
users users.UsersServiceClient
}
// NewError creates *ErrorStatusCode from error returned by handler.
//
// Used for common default response.
func (svc *Service) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
status := 500
if errors.Is(err, datastore.ErrNotExist) {
status = 404
}
if errors.Is(err, ErrPermissionDenied) {
status = 403
}
if errors.Is(err, ErrUserInfo) {
status = 401
}
return &api.ErrorStatusCode{
StatusCode: status,
Response: api.Error{
Code: int64(status),
Message: err.Error(),
},
func NewService(
db datastore.Datastore,
nats nats.JetStreamContext,
maps maps.MapsServiceClient,
users users.UsersServiceClient,
) Service {
return Service{
db: db,
nats: nats,
maps: maps,
users: users,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,92 +0,0 @@
package service_internal
import (
"context"
"encoding/json"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
func (svc *Service) CreateAuditEventAction(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataAction) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeAction,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
func (svc *Service) CreateAuditEventChangeValidatedModel(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataChangeValidatedModel) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeChangeValidatedModel,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
func (svc *Service) CreateAuditEventError(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataError) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeError,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}
func (svc *Service) CreateAuditEventCheckList(ctx context.Context, userId uint64, resource model.Resource, event_data model.AuditEventDataCheckList) error {
EventData, err := json.Marshal(event_data)
if err != nil {
return err
}
_, err = svc.DB.AuditEvents().Create(ctx, model.AuditEvent{
ID: 0,
User: userId,
ResourceType: resource.Type,
ResourceID: resource.ID,
EventType: model.AuditEventTypeCheckList,
EventData: EventData,
})
if err != nil {
return err
}
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
var(
@@ -38,12 +39,12 @@ func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params inter
ValidatedModelVersion := uint64(params.ValidatedModelVersion)
// check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional()
pmap.Add("validated_asset_id", ValidatedModelID)
pmap.Add("validated_asset_version", ValidatedModelVersion)
update := service.NewMapfixUpdate()
update.SetValidatedAssetID(ValidatedModelID)
update.SetValidatedAssetVersion(ValidatedModelVersion)
// DO NOT reset completed when validated model is updated
// pmap.Add("completed", false)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, pmap)
// update.Add("completed", false)
err := svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, update)
if err != nil {
return err
}
@@ -53,7 +54,7 @@ func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params inter
ValidatedModelVersion: ValidatedModelVersion,
}
return svc.CreateAuditEventChangeValidatedModel(
return svc.inner.CreateAuditEventChangeValidatedModel(
ctx,
ValidtorUserID,
model.Resource{
@@ -72,13 +73,14 @@ func (svc *Service) UpdateMapfixValidatedModel(ctx context.Context, params inter
func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.ActionMapfixSubmittedParams) error {
// transaction
target_status := model.MapfixStatusSubmitted
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("asset_version", params.ModelVersion)
smap.Add("display_name", params.DisplayName)
smap.Add("creator", params.Creator)
smap.Add("game_id", params.GameID)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap)
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
update.SetAssetVersion(uint64(params.ModelVersion))
update.SetDisplayName(params.DisplayName)
update.SetCreator(params.Creator)
update.SetGameID(uint32(params.GameID))
allow_statuses := []model.MapfixStatus{model.MapfixStatusSubmitting}
err := svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update)
if err != nil {
return err
}
@@ -87,7 +89,7 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -106,9 +108,9 @@ func (svc *Service) ActionMapfixSubmitted(ctx context.Context, params internal.A
func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params internal.ActionMapfixRequestChangesParams) error {
// transaction
target_status := model.MapfixStatusChangesRequested
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, smap)
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
err := svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusSubmitting}, update)
if err != nil {
return err
}
@@ -117,7 +119,7 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params inter
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -135,9 +137,9 @@ func (svc *Service) ActionMapfixRequestChanges(ctx context.Context, params inter
// POST /mapfixes/{MapfixID}/status/validator-validated
func (svc *Service) ActionMapfixValidated(ctx context.Context, params internal.ActionMapfixValidatedParams) error {
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.MapfixStatusValidated)
return svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap)
update := service.NewMapfixUpdate()
update.SetStatusID(model.MapfixStatusValidated)
return svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, update)
}
// ActionMapfixAccepted implements actionMapfixAccepted operation.
@@ -148,9 +150,9 @@ func (svc *Service) ActionMapfixValidated(ctx context.Context, params internal.A
func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.ActionMapfixAcceptedParams) error {
// transaction
target_status := model.MapfixStatusAcceptedUnvalidated
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, smap)
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
err := svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusValidating}, update)
if err != nil {
return err
}
@@ -160,7 +162,7 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.Ac
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -179,9 +181,9 @@ func (svc *Service) ActionMapfixAccepted(ctx context.Context, params internal.Ac
func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.ActionMapfixUploadedParams) error {
// transaction
target_status := model.MapfixStatusUploaded
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Mapfixes().IfStatusThenUpdate(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, smap)
update := service.NewMapfixUpdate()
update.SetStatusID(target_status)
err := svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, []model.MapfixStatus{model.MapfixStatusUploading}, update)
if err != nil {
return err
}
@@ -190,7 +192,7 @@ func (svc *Service) ActionMapfixUploaded(ctx context.Context, params internal.Ac
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -211,7 +213,7 @@ func (svc *Service) CreateMapfixAuditError(ctx context.Context, params internal.
Error: params.ErrorMessage,
}
return svc.CreateAuditEventError(
return svc.inner.CreateAuditEventError(
ctx,
ValidtorUserID,
model.Resource{
@@ -241,7 +243,7 @@ func (svc *Service) CreateMapfixAuditCheckList(ctx context.Context, check_list i
CheckList: check_list2,
}
return svc.CreateAuditEventCheckList(
return svc.inner.CreateAuditEventCheckList(
ctx,
ValidtorUserID,
model.Resource{
@@ -270,11 +272,11 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
// Check if an active mapfix with the same asset id exists
{
filter := datastore.Optional()
filter.Add("asset_id", request.AssetID)
filter.Add("asset_version", request.AssetVersion)
filter.Add("status_id", ActiveMapfixStatuses)
active_mapfixes, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{
filter := service.NewMapfixFilter()
filter.SetAssetID(AssetID)
filter.SetAssetVersion(AssetVersion)
filter.SetStatuses(ActiveMapfixStatuses)
active_mapfixes, err := svc.inner.ListMapfixes(ctx, filter, model.Page{
Number: 1,
Size: 1,
},datastore.ListSortDisabled)
@@ -286,7 +288,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
}
}
operation, err := svc.DB.Operations().Get(ctx, request.OperationID)
operation, err := svc.inner.GetOperation(ctx, request.OperationID)
if err != nil {
return nil, err
}
@@ -297,7 +299,7 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
return nil, ErrNotAssetOwner
}
mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{
mapfix, err := svc.inner.CreateMapfix(ctx, model.Mapfix{
ID: 0,
DisplayName: request.DisplayName,
Creator: request.Creator,
@@ -315,10 +317,8 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *internal.MapfixCr
}
// mark the operation as completed and provide the path
pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusCompleted)
pmap.Add("path", fmt.Sprintf("/mapfixes/%d", mapfix.ID))
err = svc.DB.Operations().Update(ctx, request.OperationID, pmap)
params := service.NewOperationCompleteParams(fmt.Sprintf("/mapfixes/%d", mapfix.ID))
err = svc.inner.CompleteOperation(ctx, request.OperationID, params)
if err != nil {
return nil, err
}

View File

@@ -3,9 +3,8 @@ package service_internal
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
// ActionOperationFailed implements actionOperationFailed operation.
@@ -14,8 +13,8 @@ import (
//
// POST /operations/{OperationID}/status/operation-failed
func (svc *Service) ActionOperationFailed(ctx context.Context, params internal.ActionOperationFailedParams) (error) {
pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusFailed)
pmap.Add("status_message", params.StatusMessage)
return svc.DB.Operations().Update(ctx, params.OperationID, pmap)
fail_params := service.NewOperationFailParams(
params.StatusMessage,
)
return svc.inner.FailOperation(ctx, params.OperationID, fail_params)
}

View File

@@ -3,9 +3,9 @@ package service_internal
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
api "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
// CreateScriptPolicy implements createScriptPolicy operation.
@@ -14,14 +14,14 @@ import (
//
// POST /script-policy
func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ScriptPolicyID, error) {
from_script, err := svc.DB.Scripts().Get(ctx, req.FromScriptID)
from_script, err := svc.inner.GetScript(ctx, req.FromScriptID)
if err != nil {
return nil, err
}
// the existence of ToScriptID does not need to be validated because it's checked by a foreign key constraint.
script, err := svc.DB.ScriptPolicy().Create(ctx, model.ScriptPolicy{
script, err := svc.inner.CreateScriptPolicy(ctx, model.ScriptPolicy{
ID: 0,
FromScriptHash: from_script.Hash,
ToScriptID: req.ToScriptID,
@@ -42,23 +42,23 @@ func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolic
//
// GET /script-policy
func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptPolicyParams) ([]api.ScriptPolicy, error) {
filter := datastore.Optional()
filter := service.NewScriptPolicyFilter()
if params.FromScriptHash.IsSet(){
hash, err := model.HashParse(params.FromScriptHash.Value)
if hash_hex, ok := params.FromScriptHash.Get(); ok{
hash_parsed, err := model.HashParse(hash_hex)
if err != nil {
return nil, err
}
filter.Add("from_script_hash", int64(hash)) // No type safety!
filter.SetFromScriptHash(int64(hash_parsed))
}
if params.ToScriptID.IsSet(){
filter.Add("to_script_id", params.ToScriptID.Value)
if to_script_id, to_script_id_ok := params.ToScriptID.Get(); to_script_id_ok{
filter.SetToScriptID(to_script_id)
}
if params.Policy.IsSet(){
filter.Add("policy", params.Policy.Value)
if policy, policy_ok := params.Policy.Get(); policy_ok{
filter.SetPolicy(policy)
}
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{
items, err := svc.inner.ListScriptPolicies(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})

View File

@@ -3,9 +3,9 @@ package service_internal
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
api "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
// CreateScript implements createScript operation.
@@ -14,7 +14,7 @@ import (
//
// POST /scripts
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ScriptID, error) {
script, err := svc.DB.Scripts().Create(ctx, model.Script{
script, err := svc.inner.CreateScript(ctx, model.Script{
ID: 0,
Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
@@ -37,29 +37,28 @@ func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*a
//
// GET /scripts
func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParams) ([]api.Script, error) {
filter := datastore.Optional()
if params.Hash.IsSet(){
hash, err := model.HashParse(params.Hash.Value)
filter := service.NewScriptFilter()
if hash_hex, ok := params.Hash.Get(); ok{
hash_parsed, err := model.HashParse(hash_hex)
if err != nil {
return nil, err
}
filter.Add("hash", int64(hash)) // No type safety!
filter.SetHash(int64(hash_parsed))
}
if params.Name.IsSet(){
filter.Add("name", params.Name.Value)
if name, name_ok := params.Name.Get(); name_ok{
filter.SetName(name)
}
if params.Source.IsSet(){
filter.Add("source", params.Source.Value)
if source, source_ok := params.Source.Get(); source_ok{
filter.SetSource(source)
}
if params.ResourceType.IsSet(){
filter.Add("resource_type", params.ResourceType.Value)
if resource_type, resource_type_ok := params.ResourceType.Get(); resource_type_ok{
filter.SetResourceType(resource_type)
}
if params.ResourceID.IsSet(){
filter.Add("resource_id", params.ResourceID.Value)
if resource_id, resource_id_ok := params.ResourceID.Get(); resource_id_ok{
filter.SetResourceID(resource_id)
}
items, err := svc.DB.Scripts().List(ctx, filter, model.Page{
items, err := svc.inner.ListScripts(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
@@ -88,7 +87,7 @@ func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParam
//
// GET /scripts/{ScriptID}
func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (*api.Script, error) {
script, err := svc.DB.Scripts().Get(ctx, params.ScriptID)
script, err := svc.inner.GetScript(ctx, params.ScriptID)
if err != nil {
return nil, err
}

View File

@@ -7,7 +7,7 @@ import (
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"github.com/nats-io/nats.go"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
const (
@@ -19,8 +19,15 @@ var (
)
type Service struct {
DB datastore.Datastore
Nats nats.JetStreamContext
inner *service.Service
}
func NewService(
inner *service.Service,
) Service {
return Service{
inner: inner,
}
}
// yay duplicate code

View File

@@ -38,12 +38,13 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i
ValidatedModelVersion := uint64(params.ValidatedModelVersion)
// check if Status is ChangesRequested|Submitted|UnderConstruction
pmap := datastore.Optional()
pmap.Add("validated_asset_id", ValidatedModelID)
pmap.Add("validated_asset_version", ValidatedModelVersion)
update := service.NewSubmissionUpdate()
update.SetValidatedAssetID(ValidatedModelID)
update.SetValidatedAssetVersion(ValidatedModelVersion)
// DO NOT reset completed when validated model is updated
// pmap.Add("completed", false)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, pmap)
// update.Add("completed", false)
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusValidating}
err := svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
if err != nil {
return err
}
@@ -53,7 +54,7 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i
ValidatedModelVersion: ValidatedModelVersion,
}
return svc.CreateAuditEventChangeValidatedModel(
return svc.inner.CreateAuditEventChangeValidatedModel(
ctx,
ValidtorUserID,
model.Resource{
@@ -72,13 +73,14 @@ func (svc *Service) UpdateSubmissionValidatedModel(ctx context.Context, params i
func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params internal.ActionSubmissionSubmittedParams) error {
// transaction
target_status := model.SubmissionStatusSubmitted
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("asset_version", params.ModelVersion)
smap.Add("display_name", params.DisplayName)
smap.Add("creator", params.Creator)
smap.Add("game_id", params.GameID)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap)
update := service.NewSubmissionUpdate()
update.SetStatusID(target_status)
update.SetAssetVersion(uint64(params.ModelVersion))
update.SetDisplayName(params.DisplayName)
update.SetCreator(params.Creator)
update.SetGameID(uint32(params.GameID))
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusSubmitting}
err := svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
if err != nil {
return err
}
@@ -87,7 +89,7 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -106,9 +108,10 @@ func (svc *Service) ActionSubmissionSubmitted(ctx context.Context, params intern
func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params internal.ActionSubmissionRequestChangesParams) error {
// transaction
target_status := model.SubmissionStatusChangesRequested
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusSubmitting}, smap)
update := service.NewSubmissionUpdate()
update.SetStatusID(target_status)
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusSubmitting}
err := svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
if err != nil {
return err
}
@@ -118,7 +121,7 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params i
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -137,9 +140,10 @@ func (svc *Service) ActionSubmissionRequestChanges(ctx context.Context, params i
func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error {
// transaction
target_status := model.SubmissionStatusValidated
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
update := service.NewSubmissionUpdate()
update.SetStatusID(target_status)
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusValidating}
err := svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
if err != nil {
return err
}
@@ -148,7 +152,7 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params intern
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -167,9 +171,10 @@ func (svc *Service) ActionSubmissionValidated(ctx context.Context, params intern
func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params internal.ActionSubmissionAcceptedParams) error {
// transaction
target_status := model.SubmissionStatusAcceptedUnvalidated
smap := datastore.Optional()
smap.Add("status_id", target_status)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusValidating}, smap)
update := service.NewSubmissionUpdate()
update.SetStatusID(target_status)
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusValidating}
err := svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
if err != nil {
return err
}
@@ -179,7 +184,7 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -198,10 +203,11 @@ func (svc *Service) ActionSubmissionAccepted(ctx context.Context, params interna
func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error {
// transaction
target_status := model.SubmissionStatusUploaded
smap := datastore.Optional()
smap.Add("status_id", target_status)
smap.Add("uploaded_asset_id", params.UploadedAssetID)
err := svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.SubmissionStatus{model.SubmissionStatusUploading}, smap)
update := service.NewSubmissionUpdate()
update.SetStatusID(target_status)
update.SetUploadedAssetID(uint64(params.UploadedAssetID))
allowed_statuses :=[]model.SubmissionStatus{model.SubmissionStatusUploading}
err := svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
if err != nil {
return err
}
@@ -210,7 +216,7 @@ func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params interna
TargetStatus: uint32(target_status),
}
return svc.CreateAuditEventAction(
return svc.inner.CreateAuditEventAction(
ctx,
ValidtorUserID,
model.Resource{
@@ -231,7 +237,7 @@ func (svc *Service) CreateSubmissionAuditError(ctx context.Context, params inter
Error: params.ErrorMessage,
}
return svc.CreateAuditEventError(
return svc.inner.CreateAuditEventError(
ctx,
ValidtorUserID,
model.Resource{
@@ -261,7 +267,7 @@ func (svc *Service) CreateSubmissionAuditCheckList(ctx context.Context, check_li
CheckList: check_list2,
}
return svc.CreateAuditEventCheckList(
return svc.inner.CreateAuditEventCheckList(
ctx,
ValidtorUserID,
model.Resource{
@@ -286,15 +292,15 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
var AssetID=uint64(request.AssetID);
var AssetVersion=uint64(request.AssetVersion);
var Status=model.SubmissionStatus(request.Status);
var roles=service.Roles(request.Roles);
var roles=model.Roles(request.Roles);
// Check if an active submission with the same asset id exists
{
filter := datastore.Optional()
filter.Add("asset_id", request.AssetID)
filter.Add("asset_version", request.AssetVersion)
filter.Add("status_id", ActiveSubmissionStatuses)
active_submissions, err := svc.DB.Submissions().List(ctx, filter, model.Page{
filter := service.NewSubmissionFilter()
filter.SetAssetID(AssetID)
filter.SetAssetVersion(AssetVersion)
filter.SetStatuses(ActiveSubmissionStatuses)
active_submissions, err := svc.inner.ListSubmissions(ctx, filter, model.Page{
Number: 1,
Size: 1,
},datastore.ListSortDisabled)
@@ -306,7 +312,7 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
}
}
operation, err := svc.DB.Operations().Get(ctx, request.OperationID)
operation, err := svc.inner.GetOperation(ctx, request.OperationID)
if err != nil {
return nil, err
}
@@ -314,13 +320,13 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
// check if user owns asset
is_submitter := operation.Owner == Submitter
// check if user is map admin
has_submission_review := roles & service.RolesSubmissionReview == service.RolesSubmissionReview
has_submission_review := roles & model.RolesSubmissionReview == model.RolesSubmissionReview
// if neither, u not allowed
if !is_submitter && !has_submission_review {
return nil, ErrNotAssetOwner
}
submission, err := svc.DB.Submissions().Create(ctx, model.Submission{
submission, err := svc.inner.CreateSubmission(ctx, model.Submission{
ID: 0,
DisplayName: request.DisplayName,
Creator: request.Creator,
@@ -336,10 +342,8 @@ func (svc *Service) CreateSubmission(ctx context.Context, request *internal.Subm
}
// mark the operation as completed and provide the path
pmap := datastore.Optional()
pmap.Add("status_id", model.OperationStatusCompleted)
pmap.Add("path", fmt.Sprintf("/submissions/%d", submission.ID))
err = svc.DB.Operations().Update(ctx, request.OperationID, pmap)
params := service.NewOperationCompleteParams(fmt.Sprintf("/submissions/%d", submission.ID))
err = svc.inner.CompleteOperation(ctx, request.OperationID, params)
if err != nil {
return nil, err
}

1068
pkg/web_api/mapfixes.go Normal file

File diff suppressed because it is too large Load Diff

112
pkg/web_api/maps.go Normal file
View File

@@ -0,0 +1,112 @@
package web_api
import (
"context"
"strings"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
// ListMaps implements listMaps operation.
//
// Get list of maps.
//
// GET /maps
func (svc *Service) ListMaps(ctx context.Context, params api.ListMapsParams) ([]api.Map, error) {
filter := service.NewMapFilter()
if display_name, display_name_ok := params.DisplayName.Get(); display_name_ok{
filter.SetDisplayName(display_name)
}
if creator, creator_ok := params.Creator.Get(); creator_ok{
filter.SetCreator(creator)
}
if game_id, game_id_ok := params.GameID.Get(); game_id_ok{
filter.SetGameID(game_id)
}
items, err := svc.inner.ListMaps(ctx,
filter,
model.Page{
Size: params.Limit,
Number: params.Page,
},
)
if err != nil {
return nil, err
}
var resp []api.Map
for _, item := range items {
resp = append(resp, api.Map{
ID: item.ID,
DisplayName: item.DisplayName,
Creator: item.Creator,
GameID: item.GameID,
Date: item.Date.Unix(),
})
}
return resp, nil
}
// GetMap implements getScript operation.
//
// Get the specified script by ID.
//
// GET /maps/{MapID}
func (svc *Service) GetMap(ctx context.Context, params api.GetMapParams) (*api.Map, error) {
mapResponse, err := svc.inner.GetMap(ctx, params.MapID)
if err != nil {
return nil, err
}
return &api.Map{
ID: mapResponse.ID,
DisplayName: mapResponse.DisplayName,
Creator: mapResponse.Creator,
GameID: mapResponse.GameID,
Date: mapResponse.Date.Unix(),
}, nil
}
// GetMapAssetLocation invokes getMapAssetLocation operation.
//
// Get location of map asset.
//
// GET /maps/{MapID}/location
func (svc *Service) GetMapAssetLocation(ctx context.Context, params api.GetMapAssetLocationParams) (ok api.GetMapAssetLocationOK, err error) {
userInfo, success := ctx.Value("UserInfo").(UserInfoHandle)
if !success {
return ok, ErrUserInfo
}
has_role, err := userInfo.HasRoleMapDownload()
if err != nil {
return ok, err
}
if !has_role {
return ok, ErrPermissionDeniedNeedRoleMapDownload
}
// Ensure map exists in the db!
// This could otherwise be used to access any asset
_, err = svc.inner.GetMap(ctx, params.MapID)
if err != nil {
return ok, err
}
info, err := svc.roblox.GetAssetLocation(roblox.GetAssetLatestRequest{
AssetID: uint64(params.MapID),
})
if err != nil{
return ok, err
}
ok.Data = strings.NewReader(info.Location)
return ok, nil
}

46
pkg/web_api/operations.go Normal file
View File

@@ -0,0 +1,46 @@
package web_api
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
)
// GetOperation implements getOperation operation.
//
// Get the specified operation by ID.
//
// GET /operations/{OperationID}
func (svc *Service) GetOperation(ctx context.Context, params api.GetOperationParams) (*api.Operation, error) {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return nil, ErrUserInfo
}
// You must be the operation owner to read it
operation, err := svc.inner.GetOperation(ctx, params.OperationID)
if err != nil {
return nil, err
}
userId, err := userInfo.GetUserID()
if err != nil {
return nil, err
}
// check if caller is the submitter
has_role := userId == operation.Owner
if !has_role {
return nil, ErrPermissionDeniedNotSubmitter
}
return &api.Operation{
OperationID: operation.ID,
Date: operation.CreatedAt.Unix(),
Owner: int64(operation.Owner),
Status: int32(operation.StatusID),
StatusMessage: operation.StatusMessage,
Path: operation.Path,
}, nil
}

View File

@@ -0,0 +1,149 @@
package web_api
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
// CreateScriptPolicy implements createScriptPolicy operation.
//
// Create a new script policy.
//
// POST /script-policy
func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolicyCreate) (*api.ScriptPolicyID, error) {
err := CheckHasRoleScriptWrite(ctx)
if err != nil {
return nil, err
}
from_script, err := svc.inner.GetScript(ctx, req.FromScriptID)
if err != nil {
return nil, err
}
// the existence of ToScriptID does not need to be validated because it's checked by a foreign key constraint.
script, err := svc.inner.CreateScriptPolicy(ctx, model.ScriptPolicy{
ID: 0,
FromScriptHash: from_script.Hash,
ToScriptID: req.ToScriptID,
Policy: model.Policy(req.Policy),
})
if err != nil {
return nil, err
}
return &api.ScriptPolicyID{
ScriptPolicyID: script.ID,
}, nil
}
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (svc *Service) ListScriptPolicy(ctx context.Context, params api.ListScriptPolicyParams) ([]api.ScriptPolicy, error) {
filter := service.NewScriptPolicyFilter()
if hash_hex, ok := params.FromScriptHash.Get(); ok{
hash_parsed, err := model.HashParse(hash_hex)
if err != nil {
return nil, err
}
filter.SetFromScriptHash(int64(hash_parsed))
}
if to_script_id, to_script_id_ok := params.ToScriptID.Get(); to_script_id_ok{
filter.SetToScriptID(to_script_id)
}
if policy, policy_ok := params.Policy.Get(); policy_ok{
filter.SetPolicy(policy)
}
items, err := svc.inner.ListScriptPolicies(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.ScriptPolicy
for _, item := range items {
resp = append(resp, api.ScriptPolicy{
ID: item.ID,
FromScriptHash: model.HashFormat(uint64(item.FromScriptHash)),
ToScriptID: item.ToScriptID,
Policy: int32(item.Policy),
})
}
return resp, nil
}
// DeleteScriptPolicy implements deleteScriptPolicy operation.
//
// Delete the specified script policy by ID.
//
// DELETE /script-policy/{ScriptPolicyID}
func (svc *Service) DeleteScriptPolicy(ctx context.Context, params api.DeleteScriptPolicyParams) error {
err := CheckHasRoleScriptWrite(ctx)
if err != nil {
return err
}
return svc.inner.DeleteScriptPolicy(ctx, params.ScriptPolicyID)
}
// GetScriptPolicy implements getScriptPolicy operation.
//
// Get the specified script policy by ID.
//
// GET /script-policy/{ScriptPolicyID}
func (svc *Service) GetScriptPolicy(ctx context.Context, params api.GetScriptPolicyParams) (*api.ScriptPolicy, error) {
policy, err := svc.inner.GetScriptPolicy(ctx, params.ScriptPolicyID)
if err != nil {
return nil, err
}
return &api.ScriptPolicy{
ID: policy.ID,
FromScriptHash: model.HashFormat(uint64(policy.FromScriptHash)),
ToScriptID: policy.ToScriptID,
Policy: int32(policy.Policy),
}, nil
}
// UpdateScriptPolicy implements updateScriptPolicy operation.
//
// Update the specified script policy by ID.
//
// POST /script-policy/{ScriptPolicyID}
func (svc *Service) UpdateScriptPolicy(ctx context.Context, req *api.ScriptPolicyUpdate, params api.UpdateScriptPolicyParams) error {
err := CheckHasRoleScriptWrite(ctx)
if err != nil {
return err
}
filter := service.NewScriptPolicyFilter()
if from_script_id, ok := req.FromScriptID.Get(); ok {
from_script, err := svc.inner.GetScript(ctx, from_script_id)
if err != nil {
return err
}
filter.SetFromScriptHash(from_script.Hash)
}
if to_script_id, to_script_id_ok := req.ToScriptID.Get(); to_script_id_ok{
filter.SetToScriptID(to_script_id)
}
if policy, policy_ok := req.Policy.Get(); policy_ok{
filter.SetPolicy(policy)
}
return svc.inner.UpdateScriptPolicy(ctx, req.ID, filter)
}

170
pkg/web_api/scripts.go Normal file
View File

@@ -0,0 +1,170 @@
package web_api
import (
"context"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/model"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
func CheckHasRoleScriptWrite(ctx context.Context) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfoHandle)
if !ok {
return ErrUserInfo
}
has_role, err := userInfo.HasRoleScriptWrite()
if err != nil {
return err
}
if !has_role {
return ErrPermissionDeniedNeedRoleScriptWrite
}
return nil
}
// CreateScript implements createScript operation.
//
// Create a new script.
//
// POST /scripts
func (svc *Service) CreateScript(ctx context.Context, req *api.ScriptCreate) (*api.ScriptID, error) {
err := CheckHasRoleScriptWrite(ctx)
if err != nil {
return nil, err
}
script, err := svc.inner.CreateScript(ctx, model.Script{
ID: 0,
Name: req.Name,
Hash: int64(model.HashSource(req.Source)),
Source: req.Source,
ResourceType: model.ResourceType(req.ResourceType),
ResourceID: req.ResourceID.Or(0),
})
if err != nil {
return nil, err
}
return &api.ScriptID{
ScriptID: script.ID,
}, nil
}
// ListScripts implements listScripts operation.
//
// Get list of scripts.
//
// GET /scripts
func (svc *Service) ListScripts(ctx context.Context, params api.ListScriptsParams) ([]api.Script, error) {
filter := service.NewScriptFilter()
if hash_hex, ok := params.Hash.Get(); ok{
hash_parsed, err := model.HashParse(hash_hex)
if err != nil {
return nil, err
}
filter.SetHash(int64(hash_parsed))
}
if name, name_ok := params.Name.Get(); name_ok{
filter.SetName(name)
}
if source, source_ok := params.Source.Get(); source_ok{
filter.SetSource(source)
}
if resource_type, resource_type_ok := params.ResourceType.Get(); resource_type_ok{
filter.SetResourceType(resource_type)
}
if resource_id, resource_id_ok := params.ResourceID.Get(); resource_id_ok{
filter.SetResourceID(resource_id)
}
items, err := svc.inner.ListScripts(ctx, filter, model.Page{
Number: params.Page,
Size: params.Limit,
})
if err != nil {
return nil, err
}
var resp []api.Script
for _, item := range items {
resp = append(resp, api.Script{
ID: item.ID,
Name: item.Name,
Hash: model.HashFormat(uint64(item.Hash)),
Source: item.Source,
ResourceType: int32(item.ResourceType),
ResourceID: item.ResourceID,
})
}
return resp, nil
}
// DeleteScript implements deleteScript operation.
//
// Delete the specified script by ID.
//
// DELETE /scripts/{ScriptID}
func (svc *Service) DeleteScript(ctx context.Context, params api.DeleteScriptParams) error {
err := CheckHasRoleScriptWrite(ctx)
if err != nil {
return err
}
return svc.inner.DeleteScript(ctx, params.ScriptID)
}
// GetScript implements getScript operation.
//
// Get the specified script by ID.
//
// GET /scripts/{ScriptID}
func (svc *Service) GetScript(ctx context.Context, params api.GetScriptParams) (*api.Script, error) {
script, err := svc.inner.GetScript(ctx, params.ScriptID)
if err != nil {
return nil, err
}
return &api.Script{
ID: script.ID,
Name: script.Name,
Hash: model.HashFormat(uint64(script.Hash)),
Source: script.Source,
ResourceType: int32(script.ResourceType),
ResourceID: script.ResourceID,
}, nil
}
// UpdateScript implements updateScript operation.
//
// Update the specified script by ID.
//
// PATCH /scripts/{ScriptID}
func (svc *Service) UpdateScript(ctx context.Context, req *api.ScriptUpdate, params api.UpdateScriptParams) error {
err := CheckHasRoleScriptWrite(ctx)
if err != nil {
return err
}
filter := service.NewScriptFilter()
if name, name_ok := req.Name.Get(); name_ok{
filter.SetName(name)
}
if source, source_ok := req.Source.Get(); source_ok{
filter.SetSource(source)
filter.SetHash(int64(model.HashSource(source)))
}
if resource_type, resource_type_ok := req.ResourceType.Get(); resource_type_ok{
filter.SetResourceType(resource_type)
}
if resource_id, resource_id_ok := req.ResourceID.Get(); resource_id_ok{
filter.SetResourceID(resource_id)
}
return svc.inner.UpdateScript(ctx, req.ID, filter)
}

View File

@@ -1,10 +1,12 @@
package service
package web_api
import (
"context"
"errors"
"git.itzana.me/strafesnet/go-grpc/auth"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
var (
@@ -14,38 +16,6 @@ var (
ErrInvalidSession = errors.New("Session invalid")
)
// Submissions roles bitflag
type Roles int32
var (
RolesSubmissionUpload Roles = 1<<6
RolesSubmissionReview Roles = 1<<5
RolesSubmissionRelease Roles = 1<<4
RolesScriptWrite Roles = 1<<3
RolesMapfixUpload Roles = 1<<2
RolesMapfixReview Roles = 1<<1
RolesMapDownload Roles = 1<<0
RolesEmpty Roles = 0
)
// StrafesNET group roles
type GroupRole int32
var (
// has ScriptWrite
RoleQuat GroupRole = 255
RoleItzaname GroupRole = 254
RoleStagingDeveloper GroupRole = 240
RolesAll Roles = ^RolesEmpty
// has SubmissionUpload
RoleMapAdmin GroupRole = 128
RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesMapCouncil
// has MapfixReview
RoleMapCouncil GroupRole = 64
RolesMapCouncil Roles = RolesMapfixReview|RolesMapfixUpload|RolesMapAccess
// access to downloading maps
RoleMapAccess GroupRole = 32
RolesMapAccess Roles = RolesMapDownload
)
type UserInfoHandle struct {
// Would love to know a better way to do this
svc *SecurityHandler
@@ -88,7 +58,7 @@ func (usr UserInfoHandle) Validate() (bool, error) {
}
return validate.Valid, nil
}
func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) {
func (usr UserInfoHandle) hasRoles(wantRoles model.Roles) (bool, error) {
haveroles, err := usr.GetRoles()
if err != nil {
return false, err
@@ -96,27 +66,27 @@ func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) {
return haveroles & wantRoles == wantRoles, nil
}
func (usr UserInfoHandle) GetRoles() (Roles, error) {
func (usr UserInfoHandle) GetRoles() (model.Roles, error) {
roles, err := usr.svc.Client.GetGroupRole(*usr.ctx, &auth.IdMessage{
SessionID: usr.sessionId,
})
if err != nil {
return RolesEmpty, err
return model.RolesEmpty, err
}
// map roles into bitflag
rolesBitflag := RolesEmpty;
rolesBitflag := model.RolesEmpty;
for _, r := range roles.Roles {
switch GroupRole(r.Rank){
case RoleQuat, RoleItzaname, RoleStagingDeveloper:
rolesBitflag|=RolesAll
case RoleMapAdmin:
rolesBitflag|=RolesMapAdmin
case RoleMapCouncil:
rolesBitflag|=RolesMapCouncil
case RoleMapAccess:
rolesBitflag|=RolesMapAccess
switch model.GroupRole(r.Rank){
case model.RoleQuat, model.RoleItzaname, model.RoleStagingDeveloper:
rolesBitflag|=model.RolesAll
case model.RoleMapAdmin:
rolesBitflag|=model.RolesMapAdmin
case model.RoleMapCouncil:
rolesBitflag|=model.RolesMapCouncil
case model.RoleMapAccess:
rolesBitflag|=model.RolesMapAccess
}
}
return rolesBitflag, nil
@@ -124,25 +94,25 @@ func (usr UserInfoHandle) GetRoles() (Roles, error) {
// RoleThumbnail
func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) {
return usr.hasRoles(RolesMapfixUpload)
return usr.hasRoles(model.RolesMapfixUpload)
}
func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) {
return usr.hasRoles(RolesMapfixReview)
return usr.hasRoles(model.RolesMapfixReview)
}
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
return usr.hasRoles(RolesMapDownload)
return usr.hasRoles(model.RolesMapDownload)
}
func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) {
return usr.hasRoles(RolesSubmissionRelease)
return usr.hasRoles(model.RolesSubmissionRelease)
}
func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) {
return usr.hasRoles(RolesSubmissionUpload)
return usr.hasRoles(model.RolesSubmissionUpload)
}
func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
return usr.hasRoles(RolesSubmissionReview)
return usr.hasRoles(model.RolesSubmissionReview)
}
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
return usr.hasRoles(RolesScriptWrite)
return usr.hasRoles(model.RolesScriptWrite)
}
/// Not implemented
func (usr UserInfoHandle) HasRoleMaptest() (bool, error) {

68
pkg/web_api/service.go Normal file
View File

@@ -0,0 +1,68 @@
package web_api
import (
"context"
"errors"
"fmt"
"git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/roblox"
"git.itzana.me/strafesnet/maps-service/pkg/service"
)
var (
// ErrPermissionDenied caller does not have the required role
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)
ErrPermissionDeniedNeedRoleMapfixUpload = fmt.Errorf("%w: Need Role MapfixUpload", ErrPermissionDenied)
ErrPermissionDeniedNeedRoleMapfixReview = fmt.Errorf("%w: Need Role MapfixReview", 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)
ErrNegativeID = errors.New("A negative ID was provided")
)
type Service struct {
inner *service.Service
roblox roblox.Client
}
func NewService(
inner *service.Service,
roblox roblox.Client,
) Service {
return Service{
inner: inner,
roblox: roblox,
}
}
// NewError creates *ErrorStatusCode from error returned by handler.
//
// Used for common default response.
func (svc *Service) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
status := 500
if errors.Is(err, datastore.ErrNotExist) {
status = 404
}
if errors.Is(err, ErrPermissionDenied) {
status = 403
}
if errors.Is(err, ErrUserInfo) {
status = 401
}
return &api.ErrorStatusCode{
StatusCode: status,
Response: api.Error{
Code: int64(status),
Message: err.Error(),
},
}
}

View File

@@ -1,4 +1,4 @@
package service
package web_api
import (
"context"

1174
pkg/web_api/submissions.go Normal file

File diff suppressed because it is too large Load Diff