Compare commits

...

14 Commits

Author SHA1 Message Date
bf0a6c63cd form values encode to json
All checks were successful
continuous-integration/drone/push Build is passing
explicit errors for each value is implemented -
TODO: show the error to the user with html text instead of printing it
2024-12-15 14:26:00 -05:00
51d812b3ad web: change buttons
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-14 11:35:11 -08:00
a83ab272bb releaser: run me to batch release maps 2024-12-14 11:18:32 -08:00
4ce5d5e535 validation: pull out submissions api
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-14 11:18:25 -08:00
1450c0f3a2 validation: remove map publishing 2024-12-14 04:02:34 -08:00
76abcf0a34 validation: fs unneeded
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-14 03:17:34 -08:00
d4303612ac web: material ui form inputs on the submission page, "Target" radio buttons
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-14 03:25:04 -05:00
5e5caae6c3 Add drone ci
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-14 02:14:19 -05:00
673152bc0d openapi: list filter belongs in request body 2024-12-13 22:54:22 -08:00
203ae67384 web: maybe /submit instead of /submission_new 2024-12-14 01:53:44 -05:00
e31dec6424 web: form page base concept 2024-12-14 01:49:36 -05:00
00fdbd9611 openapi: GET /script-policy endpoint to list script policies 2024-12-13 22:15:05 -08:00
346f49610d script review: introduce None policy 2024-12-13 21:50:19 -08:00
ae6e968135 web: remove Roblox.ts 2024-12-14 00:37:52 -05:00
53 changed files with 5551 additions and 942 deletions

88
.drone.yml Normal file
View File

@ -0,0 +1,88 @@
---
kind: pipeline
type: docker
platform:
os: linux
arch: amd64
steps:
- name: api
image: plugins/docker
settings:
registry: registry.itzana.me
repo: registry.itzana.me/strafesnet/maptest-api
tags:
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
- ${DRONE_BRANCH}
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
dockerfile: Containerfile
context: .
when:
branch:
- master
- staging
- name: frontend
image: plugins/docker
settings:
registry: registry.itzana.me
repo: registry.itzana.me/strafesnet/maptest-frontend
tags:
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
- ${DRONE_BRANCH}
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
dockerfile: web/Containerfile
context: web
when:
branch:
- master
- staging
- name: validator
image: plugins/docker
settings:
registry: registry.itzana.me
repo: registry.itzana.me/strafesnet/maptest-validator
tags:
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
- ${DRONE_BRANCH}
username:
from_secret: REGISTRY_USER
password:
from_secret: REGISTRY_PASS
dockerfile: validation/Containerfile
context: validation
when:
branch:
- master
- staging
- name: deploy
image: argoproj/argocd:latest
commands:
- echo "Deploy!" # Not going to do actually do this until
environment:
USERNAME:
from_secret: ARGO_USER
PASSWORD:
from_secret: ARGO_PASS
depends_on:
- api
- frontend
- validator
when:
branch:
- master
- staging
---
kind: signature
hmac: 9958fd5b01af1ebcc75f7277fe71eb5336b899445c359cecf1b14e83b3d05059
...

View File

@ -7,9 +7,6 @@ WORKDIR /app
# Copy go.mod and go.sum files # Copy go.mod and go.sum files
COPY go.mod go.sum ./ COPY go.mod go.sum ./
# Download dependencies
RUN --mount=type=secret,id=netrc,dst=/root/.netrc go mod download
# Copy the entire project # Copy the entire project
COPY . . COPY . .

View File

@ -1,8 +1,12 @@
.PHONY: maps-service web validation
maps-service: maps-service:
DOCKER_BUILDKIT=1 docker build . -f Containerfile -t maps-service \ DOCKER_BUILDKIT=1 docker build . -f Containerfile -t maps-service
--secret id=netrc,src=/home/quat/.netrc
web: web:
docker build web -f web/Containerfile -t maps-service-web docker build web -f web/Containerfile -t maps-service-web
validation: validation:
docker build validation -f validation/Containerfile -t maps-service-validation docker build validation -f validation/Containerfile -t maps-service-validation
all: maps-service web validation
.PHONY: maps-service web validation

View File

@ -19,16 +19,18 @@ paths:
operationId: listSubmissions operationId: listSubmissions
tags: tags:
- Submissions - Submissions
parameters: requestBody:
- name: page
in: query
required: true required: true
content:
application/json:
schema: schema:
required:
- Page
type: object
properties:
Page:
$ref: "#/components/schemas/Pagination" $ref: "#/components/schemas/Pagination"
- name: filter Filter:
in: query
required: false
schema:
$ref: "#/components/schemas/SubmissionFilter" $ref: "#/components/schemas/SubmissionFilter"
security: security:
- cookieAuth: [] - cookieAuth: []
@ -295,6 +297,41 @@ paths:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/script-policy: /script-policy:
get:
summary: Get list of script policies
operationId: listScriptPolicy
tags:
- ScriptPolicy
requestBody:
required: true
content:
application/json:
schema:
required:
- Page
type: object
properties:
Page:
$ref: "#/components/schemas/Pagination"
Filter:
$ref: "#/components/schemas/ScriptPolicyFilter"
security:
- cookieAuth: []
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ScriptPolicy"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post: post:
summary: Create a new script policy summary: Create a new script policy
operationId: createScriptPolicy operationId: createScriptPolicy
@ -715,6 +752,22 @@ components:
Policy: Policy:
type: integer type: integer
format: int32 format: int32
ScriptPolicyFilter:
type: object
properties:
ID:
type: integer
format: int64
FromScriptHash:
type: string
minLength: 16
maxLength: 16
ToScriptID:
type: integer
format: int64
Policy:
type: integer
format: int32
ScriptPolicyCreate: ScriptPolicyCreate:
required: required:
- FromScriptID - FromScriptID

View File

@ -126,12 +126,18 @@ type Invoker interface {
// //
// GET /submissions/{SubmissionID} // GET /submissions/{SubmissionID}
GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error) GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error)
// ListScriptPolicy invokes listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
ListScriptPolicy(ctx context.Context, request *ListScriptPolicyReq) ([]ScriptPolicy, error)
// ListSubmissions invokes listSubmissions operation. // ListSubmissions invokes listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
ListSubmissions(ctx context.Context, params ListSubmissionsParams) ([]Submission, error) ListSubmissions(ctx context.Context, request *ListSubmissionsReq) ([]Submission, error)
// SetSubmissionCompleted invokes setSubmissionCompleted operation. // SetSubmissionCompleted invokes setSubmissionCompleted operation.
// //
// Retrieve map with ID. // Retrieve map with ID.
@ -2200,17 +2206,125 @@ func (c *Client) sendGetSubmission(ctx context.Context, params GetSubmissionPara
return result, nil return result, nil
} }
// ListScriptPolicy invokes listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (c *Client) ListScriptPolicy(ctx context.Context, request *ListScriptPolicyReq) ([]ScriptPolicy, error) {
res, err := c.sendListScriptPolicy(ctx, request)
return res, err
}
func (c *Client) sendListScriptPolicy(ctx context.Context, request *ListScriptPolicyReq) (res []ScriptPolicy, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("listScriptPolicy"),
semconv.HTTPRequestMethodKey.String("GET"),
semconv.HTTPRouteKey.String("/script-policy"),
}
// Run stopwatch.
startTime := time.Now()
defer func() {
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedDuration := time.Since(startTime)
c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...))
}()
// Increment request counter.
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
// Start a span for this request.
ctx, span := c.cfg.Tracer.Start(ctx, ListScriptPolicyOperation,
trace.WithAttributes(otelAttrs...),
clientSpanKind,
)
// Track stage for error reporting.
var stage string
defer func() {
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, stage)
c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
}
span.End()
}()
stage = "BuildURL"
u := uri.Clone(c.requestURL(ctx))
var pathParts [1]string
pathParts[0] = "/script-policy"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "GET", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
if err := encodeListScriptPolicyRequest(request, r); err != nil {
return res, errors.Wrap(err, "encode request")
}
{
type bitset = [1]uint8
var satisfied bitset
{
stage = "Security:CookieAuth"
switch err := c.securityCookieAuth(ctx, ListScriptPolicyOperation, r); {
case err == nil: // if NO error
satisfied[0] |= 1 << 0
case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
// Skip this security.
default:
return res, errors.Wrap(err, "security \"CookieAuth\"")
}
}
if ok := func() bool {
nextRequirement:
for _, requirement := range []bitset{
{0b00000001},
} {
for i, mask := range requirement {
if satisfied[i]&mask != mask {
continue nextRequirement
}
}
return true
}
return false
}(); !ok {
return res, ogenerrors.ErrSecurityRequirementIsNotSatisfied
}
}
stage = "SendRequest"
resp, err := c.cfg.Client.Do(r)
if err != nil {
return res, errors.Wrap(err, "do request")
}
defer resp.Body.Close()
stage = "DecodeResponse"
result, err := decodeListScriptPolicyResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ListSubmissions invokes listSubmissions operation. // ListSubmissions invokes listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
func (c *Client) ListSubmissions(ctx context.Context, params ListSubmissionsParams) ([]Submission, error) { func (c *Client) ListSubmissions(ctx context.Context, request *ListSubmissionsReq) ([]Submission, error) {
res, err := c.sendListSubmissions(ctx, params) res, err := c.sendListSubmissions(ctx, request)
return res, err return res, err
} }
func (c *Client) sendListSubmissions(ctx context.Context, params ListSubmissionsParams) (res []Submission, err error) { func (c *Client) sendListSubmissions(ctx context.Context, request *ListSubmissionsReq) (res []Submission, err error) {
otelAttrs := []attribute.KeyValue{ otelAttrs := []attribute.KeyValue{
otelogen.OperationID("listSubmissions"), otelogen.OperationID("listSubmissions"),
semconv.HTTPRequestMethodKey.String("GET"), semconv.HTTPRequestMethodKey.String("GET"),
@ -2250,46 +2364,14 @@ func (c *Client) sendListSubmissions(ctx context.Context, params ListSubmissions
pathParts[0] = "/submissions" pathParts[0] = "/submissions"
uri.AddPathParts(u, pathParts[:]...) uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "page" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "page",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
return params.Page.EncodeURI(e)
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
{
// Encode "filter" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "filter",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := params.Filter.Get(); ok {
return val.EncodeURI(e)
}
return nil
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest" stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "GET", u) r, err := ht.NewRequest(ctx, "GET", u)
if err != nil { if err != nil {
return res, errors.Wrap(err, "create request") return res, errors.Wrap(err, "create request")
} }
if err := encodeListSubmissionsRequest(request, r); err != nil {
return res, errors.Wrap(err, "encode request")
}
{ {
type bitset = [1]uint8 type bitset = [1]uint8

View File

@ -3253,6 +3253,201 @@ func (s *Server) handleGetSubmissionRequest(args [1]string, argsEscaped bool, w
} }
} }
// handleListScriptPolicyRequest handles listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (s *Server) handleListScriptPolicyRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("listScriptPolicy"),
semconv.HTTPRequestMethodKey.String("GET"),
semconv.HTTPRouteKey.String("/script-policy"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ListScriptPolicyOperation,
trace.WithAttributes(otelAttrs...),
serverSpanKind,
)
defer span.End()
// Add Labeler to context.
labeler := &Labeler{attrs: otelAttrs}
ctx = contextWithLabeler(ctx, labeler)
// Run stopwatch.
startTime := time.Now()
defer func() {
elapsedDuration := time.Since(startTime)
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
code := statusWriter.status
if code != 0 {
codeAttr := semconv.HTTPResponseStatusCode(code)
attrs = append(attrs, codeAttr)
span.SetAttributes(codeAttr)
}
attrOpt := metric.WithAttributes(attrs...)
// Increment request counter.
s.requests.Add(ctx, 1, attrOpt)
// Use floating point division here for higher precision (instead of Millisecond method).
s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt)
}()
var (
recordError = func(stage string, err error) {
span.RecordError(err)
// https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status
// Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges,
// unless there was another error (e.g., network error receiving the response body; or 3xx codes with
// max redirects exceeded), in which case status MUST be set to Error.
code := statusWriter.status
if code >= 100 && code < 500 {
span.SetStatus(codes.Error, stage)
}
attrSet := labeler.AttributeSet()
attrs := attrSet.ToSlice()
if code != 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(code))
}
s.errors.Add(ctx, 1, metric.WithAttributes(attrs...))
}
err error
opErrContext = ogenerrors.OperationContext{
Name: ListScriptPolicyOperation,
ID: "listScriptPolicy",
}
)
{
type bitset = [1]uint8
var satisfied bitset
{
sctx, ok, err := s.securityCookieAuth(ctx, ListScriptPolicyOperation, r)
if err != nil {
err = &ogenerrors.SecurityError{
OperationContext: opErrContext,
Security: "CookieAuth",
Err: err,
}
if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil {
defer recordError("Security:CookieAuth", err)
}
return
}
if ok {
satisfied[0] |= 1 << 0
ctx = sctx
}
}
if ok := func() bool {
nextRequirement:
for _, requirement := range []bitset{
{0b00000001},
} {
for i, mask := range requirement {
if satisfied[i]&mask != mask {
continue nextRequirement
}
}
return true
}
return false
}(); !ok {
err = &ogenerrors.SecurityError{
OperationContext: opErrContext,
Err: ogenerrors.ErrSecurityRequirementIsNotSatisfied,
}
if encodeErr := encodeErrorResponse(s.h.NewError(ctx, err), w, span); encodeErr != nil {
defer recordError("Security", err)
}
return
}
}
request, close, err := s.decodeListScriptPolicyRequest(r)
if err != nil {
err = &ogenerrors.DecodeRequestError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeRequest", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
defer func() {
if err := close(); err != nil {
recordError("CloseRequest", err)
}
}()
var response []ScriptPolicy
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ListScriptPolicyOperation,
OperationSummary: "Get list of script policies",
OperationID: "listScriptPolicy",
Body: request,
Params: middleware.Parameters{},
Raw: r,
}
type (
Request = *ListScriptPolicyReq
Params = struct{}
Response = []ScriptPolicy
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
nil,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
response, err = s.h.ListScriptPolicy(ctx, request)
return response, err
},
)
} else {
response, err = s.h.ListScriptPolicy(ctx, request)
}
if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
if err := encodeErrorResponse(errRes, w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if errors.Is(err, ht.ErrNotImplemented) {
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil {
defer recordError("Internal", err)
}
return
}
if err := encodeListScriptPolicyResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleListSubmissionsRequest handles listSubmissions operation. // handleListSubmissionsRequest handles listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
@ -3373,16 +3568,21 @@ func (s *Server) handleListSubmissionsRequest(args [0]string, argsEscaped bool,
return return
} }
} }
params, err := decodeListSubmissionsParams(args, argsEscaped, r) request, close, err := s.decodeListSubmissionsRequest(r)
if err != nil { if err != nil {
err = &ogenerrors.DecodeParamsError{ err = &ogenerrors.DecodeRequestError{
OperationContext: opErrContext, OperationContext: opErrContext,
Err: err, Err: err,
} }
defer recordError("DecodeParams", err) defer recordError("DecodeRequest", err)
s.cfg.ErrorHandler(ctx, w, r, err) s.cfg.ErrorHandler(ctx, w, r, err)
return return
} }
defer func() {
if err := close(); err != nil {
recordError("CloseRequest", err)
}
}()
var response []Submission var response []Submission
if m := s.cfg.Middleware; m != nil { if m := s.cfg.Middleware; m != nil {
@ -3391,23 +3591,14 @@ func (s *Server) handleListSubmissionsRequest(args [0]string, argsEscaped bool,
OperationName: ListSubmissionsOperation, OperationName: ListSubmissionsOperation,
OperationSummary: "Get list of submissions", OperationSummary: "Get list of submissions",
OperationID: "listSubmissions", OperationID: "listSubmissions",
Body: nil, Body: request,
Params: middleware.Parameters{ Params: middleware.Parameters{},
{
Name: "page",
In: "query",
}: params.Page,
{
Name: "filter",
In: "query",
}: params.Filter,
},
Raw: r, Raw: r,
} }
type ( type (
Request = struct{} Request = *ListSubmissionsReq
Params = ListSubmissionsParams Params = struct{}
Response = []Submission Response = []Submission
) )
response, err = middleware.HookMiddleware[ response, err = middleware.HookMiddleware[
@ -3417,14 +3608,14 @@ func (s *Server) handleListSubmissionsRequest(args [0]string, argsEscaped bool,
]( ](
m, m,
mreq, mreq,
unpackListSubmissionsParams, nil,
func(ctx context.Context, request Request, params Params) (response Response, err error) { func(ctx context.Context, request Request, params Params) (response Response, err error) {
response, err = s.h.ListSubmissions(ctx, params) response, err = s.h.ListSubmissions(ctx, request)
return response, err return response, err
}, },
) )
} else { } else {
response, err = s.h.ListSubmissions(ctx, params) response, err = s.h.ListSubmissions(ctx, request)
} }
if err != nil { if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {

View File

@ -221,6 +221,228 @@ func (s *ID) UnmarshalJSON(data []byte) error {
return s.Decode(d) return s.Decode(d)
} }
// Encode implements json.Marshaler.
func (s *ListScriptPolicyReq) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ListScriptPolicyReq) encodeFields(e *jx.Encoder) {
{
e.FieldStart("Page")
s.Page.Encode(e)
}
{
if s.Filter.Set {
e.FieldStart("Filter")
s.Filter.Encode(e)
}
}
}
var jsonFieldsNameOfListScriptPolicyReq = [2]string{
0: "Page",
1: "Filter",
}
// Decode decodes ListScriptPolicyReq from json.
func (s *ListScriptPolicyReq) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ListScriptPolicyReq to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "Page":
requiredBitSet[0] |= 1 << 0
if err := func() error {
if err := s.Page.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Page\"")
}
case "Filter":
if err := func() error {
s.Filter.Reset()
if err := s.Filter.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Filter\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ListScriptPolicyReq")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfListScriptPolicyReq) {
name = jsonFieldsNameOfListScriptPolicyReq[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *ListScriptPolicyReq) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ListScriptPolicyReq) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *ListSubmissionsReq) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ListSubmissionsReq) encodeFields(e *jx.Encoder) {
{
e.FieldStart("Page")
s.Page.Encode(e)
}
{
if s.Filter.Set {
e.FieldStart("Filter")
s.Filter.Encode(e)
}
}
}
var jsonFieldsNameOfListSubmissionsReq = [2]string{
0: "Page",
1: "Filter",
}
// Decode decodes ListSubmissionsReq from json.
func (s *ListSubmissionsReq) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ListSubmissionsReq to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "Page":
requiredBitSet[0] |= 1 << 0
if err := func() error {
if err := s.Page.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Page\"")
}
case "Filter":
if err := func() error {
s.Filter.Reset()
if err := s.Filter.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Filter\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ListSubmissionsReq")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfListSubmissionsReq) {
name = jsonFieldsNameOfListSubmissionsReq[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *ListSubmissionsReq) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ListSubmissionsReq) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode encodes int32 as json. // Encode encodes int32 as json.
func (o OptInt32) Encode(e *jx.Encoder) { func (o OptInt32) Encode(e *jx.Encoder) {
if !o.Set { if !o.Set {
@ -291,6 +513,39 @@ func (s *OptInt64) UnmarshalJSON(data []byte) error {
return s.Decode(d) return s.Decode(d)
} }
// Encode encodes ScriptPolicyFilter as json.
func (o OptScriptPolicyFilter) Encode(e *jx.Encoder) {
if !o.Set {
return
}
o.Value.Encode(e)
}
// Decode decodes ScriptPolicyFilter from json.
func (o *OptScriptPolicyFilter) Decode(d *jx.Decoder) error {
if o == nil {
return errors.New("invalid: unable to decode OptScriptPolicyFilter to nil")
}
o.Set = true
if err := o.Value.Decode(d); err != nil {
return err
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s OptScriptPolicyFilter) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *OptScriptPolicyFilter) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode encodes string as json. // Encode encodes string as json.
func (o OptString) Encode(e *jx.Encoder) { func (o OptString) Encode(e *jx.Encoder) {
if !o.Set { if !o.Set {
@ -326,6 +581,152 @@ func (s *OptString) UnmarshalJSON(data []byte) error {
return s.Decode(d) return s.Decode(d)
} }
// Encode encodes SubmissionFilter as json.
func (o OptSubmissionFilter) Encode(e *jx.Encoder) {
if !o.Set {
return
}
o.Value.Encode(e)
}
// Decode decodes SubmissionFilter from json.
func (o *OptSubmissionFilter) Decode(d *jx.Decoder) error {
if o == nil {
return errors.New("invalid: unable to decode OptSubmissionFilter to nil")
}
o.Set = true
if err := o.Value.Decode(d); err != nil {
return err
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s OptSubmissionFilter) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *OptSubmissionFilter) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler.
func (s *Pagination) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *Pagination) encodeFields(e *jx.Encoder) {
{
e.FieldStart("Page")
e.Int32(s.Page)
}
{
e.FieldStart("Limit")
e.Int32(s.Limit)
}
}
var jsonFieldsNameOfPagination = [2]string{
0: "Page",
1: "Limit",
}
// Decode decodes Pagination from json.
func (s *Pagination) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Pagination to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "Page":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int32()
s.Page = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Page\"")
}
case "Limit":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Int32()
s.Limit = int32(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Limit\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Pagination")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000011,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfPagination) {
name = jsonFieldsNameOfPagination[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *Pagination) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *Pagination) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler. // Encode implements json.Marshaler.
func (s *Script) Encode(e *jx.Encoder) { func (s *Script) Encode(e *jx.Encoder) {
e.ObjStart() e.ObjStart()
@ -863,6 +1264,120 @@ func (s *ScriptPolicyCreate) UnmarshalJSON(data []byte) error {
return s.Decode(d) return s.Decode(d)
} }
// Encode implements json.Marshaler.
func (s *ScriptPolicyFilter) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *ScriptPolicyFilter) encodeFields(e *jx.Encoder) {
{
if s.ID.Set {
e.FieldStart("ID")
s.ID.Encode(e)
}
}
{
if s.FromScriptHash.Set {
e.FieldStart("FromScriptHash")
s.FromScriptHash.Encode(e)
}
}
{
if s.ToScriptID.Set {
e.FieldStart("ToScriptID")
s.ToScriptID.Encode(e)
}
}
{
if s.Policy.Set {
e.FieldStart("Policy")
s.Policy.Encode(e)
}
}
}
var jsonFieldsNameOfScriptPolicyFilter = [4]string{
0: "ID",
1: "FromScriptHash",
2: "ToScriptID",
3: "Policy",
}
// Decode decodes ScriptPolicyFilter from json.
func (s *ScriptPolicyFilter) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode ScriptPolicyFilter to nil")
}
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "ID":
if err := func() error {
s.ID.Reset()
if err := s.ID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ID\"")
}
case "FromScriptHash":
if err := func() error {
s.FromScriptHash.Reset()
if err := s.FromScriptHash.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"FromScriptHash\"")
}
case "ToScriptID":
if err := func() error {
s.ToScriptID.Reset()
if err := s.ToScriptID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ToScriptID\"")
}
case "Policy":
if err := func() error {
s.Policy.Reset()
if err := s.Policy.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Policy\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode ScriptPolicyFilter")
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *ScriptPolicyFilter) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *ScriptPolicyFilter) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler. // Encode implements json.Marshaler.
func (s *ScriptPolicyUpdate) Encode(e *jx.Encoder) { func (s *ScriptPolicyUpdate) Encode(e *jx.Encoder) {
e.ObjStart() e.ObjStart()
@ -1621,3 +2136,150 @@ func (s *SubmissionCreate) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data) d := jx.DecodeBytes(data)
return s.Decode(d) return s.Decode(d)
} }
// Encode implements json.Marshaler.
func (s *SubmissionFilter) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *SubmissionFilter) encodeFields(e *jx.Encoder) {
{
e.FieldStart("ID")
e.Int64(s.ID)
}
{
if s.DisplayName.Set {
e.FieldStart("DisplayName")
s.DisplayName.Encode(e)
}
}
{
if s.Creator.Set {
e.FieldStart("Creator")
s.Creator.Encode(e)
}
}
{
if s.GameID.Set {
e.FieldStart("GameID")
s.GameID.Encode(e)
}
}
}
var jsonFieldsNameOfSubmissionFilter = [4]string{
0: "ID",
1: "DisplayName",
2: "Creator",
3: "GameID",
}
// Decode decodes SubmissionFilter from json.
func (s *SubmissionFilter) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode SubmissionFilter to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "ID":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.ID = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ID\"")
}
case "DisplayName":
if err := func() error {
s.DisplayName.Reset()
if err := s.DisplayName.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"DisplayName\"")
}
case "Creator":
if err := func() error {
s.Creator.Reset()
if err := s.Creator.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Creator\"")
}
case "GameID":
if err := func() error {
s.GameID.Reset()
if err := s.GameID.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"GameID\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode SubmissionFilter")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfSubmissionFilter) {
name = jsonFieldsNameOfSubmissionFilter[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s *SubmissionFilter) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *SubmissionFilter) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}

View File

@ -23,6 +23,7 @@ const (
GetScriptPolicyOperation OperationName = "GetScriptPolicy" GetScriptPolicyOperation OperationName = "GetScriptPolicy"
GetScriptPolicyFromHashOperation OperationName = "GetScriptPolicyFromHash" GetScriptPolicyFromHashOperation OperationName = "GetScriptPolicyFromHash"
GetSubmissionOperation OperationName = "GetSubmission" GetSubmissionOperation OperationName = "GetSubmission"
ListScriptPolicyOperation OperationName = "ListScriptPolicy"
ListSubmissionsOperation OperationName = "ListSubmissions" ListSubmissionsOperation OperationName = "ListSubmissions"
SetSubmissionCompletedOperation OperationName = "SetSubmissionCompleted" SetSubmissionCompletedOperation OperationName = "SetSubmissionCompleted"
UpdateScriptOperation OperationName = "UpdateScript" UpdateScriptOperation OperationName = "UpdateScript"

View File

@ -954,117 +954,6 @@ func decodeGetSubmissionParams(args [1]string, argsEscaped bool, r *http.Request
return params, nil return params, nil
} }
// ListSubmissionsParams is parameters of listSubmissions operation.
type ListSubmissionsParams struct {
Page Pagination
Filter OptSubmissionFilter
}
func unpackListSubmissionsParams(packed middleware.Parameters) (params ListSubmissionsParams) {
{
key := middleware.ParameterKey{
Name: "page",
In: "query",
}
params.Page = packed[key].(Pagination)
}
{
key := middleware.ParameterKey{
Name: "filter",
In: "query",
}
if v, ok := packed[key]; ok {
params.Filter = v.(OptSubmissionFilter)
}
}
return params
}
func decodeListSubmissionsParams(args [0]string, argsEscaped bool, r *http.Request) (params ListSubmissionsParams, _ error) {
q := uri.NewQueryDecoder(r.URL.Query())
// Decode query: page.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "page",
Style: uri.QueryStyleForm,
Explode: true,
Fields: []uri.QueryParameterObjectField{{Name: "Page", Required: true}, {Name: "Limit", Required: true}},
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
return params.Page.DecodeURI(d)
}); err != nil {
return err
}
if err := func() error {
if err := params.Page.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
} else {
return validate.ErrFieldRequired
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "page",
In: "query",
Err: err,
}
}
// Decode query: filter.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "filter",
Style: uri.QueryStyleForm,
Explode: true,
Fields: []uri.QueryParameterObjectField{{Name: "ID", Required: true}, {Name: "DisplayName", Required: false}, {Name: "Creator", Required: false}, {Name: "GameID", Required: false}},
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
var paramsDotFilterVal SubmissionFilter
if err := func() error {
return paramsDotFilterVal.DecodeURI(d)
}(); err != nil {
return err
}
params.Filter.SetTo(paramsDotFilterVal)
return nil
}); err != nil {
return err
}
if err := func() error {
if value, ok := params.Filter.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "filter",
In: "query",
Err: err,
}
}
return params, nil
}
// SetSubmissionCompletedParams is parameters of setSubmissionCompleted operation. // SetSubmissionCompletedParams is parameters of setSubmissionCompleted operation.
type SetSubmissionCompletedParams struct { type SetSubmissionCompletedParams struct {
// The unique identifier for a submission. // The unique identifier for a submission.

View File

@ -220,6 +220,148 @@ func (s *Server) decodeCreateSubmissionRequest(r *http.Request) (
} }
} }
func (s *Server) decodeListScriptPolicyRequest(r *http.Request) (
req *ListScriptPolicyReq,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = multierr.Append(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = multierr.Append(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
if err != nil {
return req, close, err
}
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
}
d := jx.DecodeBytes(buf)
var request ListScriptPolicyReq
if err := func() error {
if err := request.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return req, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeListSubmissionsRequest(r *http.Request) (
req *ListSubmissionsReq,
close func() error,
rerr error,
) {
var closers []func() error
close = func() error {
var merr error
// Close in reverse order, to match defer behavior.
for i := len(closers) - 1; i >= 0; i-- {
c := closers[i]
merr = multierr.Append(merr, c())
}
return merr
}
defer func() {
if rerr != nil {
rerr = multierr.Append(rerr, close())
}
}()
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return req, close, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
if r.ContentLength == 0 {
return req, close, validate.ErrBodyRequired
}
buf, err := io.ReadAll(r.Body)
if err != nil {
return req, close, err
}
if len(buf) == 0 {
return req, close, validate.ErrBodyRequired
}
d := jx.DecodeBytes(buf)
var request ListSubmissionsReq
if err := func() error {
if err := request.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return req, close, err
}
if err := func() error {
if err := request.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return req, close, errors.Wrap(err, "validate")
}
return &request, close, nil
default:
return req, close, validate.InvalidContentType(ct)
}
}
func (s *Server) decodeUpdateScriptRequest(r *http.Request) ( func (s *Server) decodeUpdateScriptRequest(r *http.Request) (
req *ScriptUpdate, req *ScriptUpdate,
close func() error, close func() error,

View File

@ -53,6 +53,34 @@ func encodeCreateSubmissionRequest(
return nil return nil
} }
func encodeListScriptPolicyRequest(
req *ListScriptPolicyReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeListSubmissionsRequest(
req *ListSubmissionsReq,
r *http.Request,
) error {
const contentType = "application/json"
e := new(jx.Encoder)
{
req.Encode(e)
}
encoded := e.Bytes()
ht.SetBody(r, bytes.NewReader(encoded), contentType)
return nil
}
func encodeUpdateScriptRequest( func encodeUpdateScriptRequest(
req *ScriptUpdate, req *ScriptUpdate,
r *http.Request, r *http.Request,

View File

@ -1142,6 +1142,123 @@ func decodeGetSubmissionResponse(resp *http.Response) (res *Submission, _ error)
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeListScriptPolicyResponse(resp *http.Response) (res []ScriptPolicy, _ error) {
switch resp.StatusCode {
case 200:
// Code 200.
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response []ScriptPolicy
if err := func() error {
response = make([]ScriptPolicy, 0)
if err := d.Arr(func(d *jx.Decoder) error {
var elem ScriptPolicy
if err := elem.Decode(d); err != nil {
return err
}
response = append(response, elem)
return nil
}); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
// Validate response.
if err := func() error {
if response == nil {
return errors.New("nil is invalid value")
}
var failures []validate.FieldError
for i, elem := range response {
if err := func() error {
if err := elem.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: fmt.Sprintf("[%d]", i),
Error: err,
})
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}(); err != nil {
return res, errors.Wrap(err, "validate")
}
return response, nil
default:
return res, validate.InvalidContentType(ct)
}
}
// Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) {
ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return res, errors.Wrap(err, "parse media type")
}
switch {
case ct == "application/json":
buf, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
d := jx.DecodeBytes(buf)
var response Error
if err := func() error {
if err := response.Decode(d); err != nil {
return err
}
if err := d.Skip(); err != io.EOF {
return errors.New("unexpected trailing data")
}
return nil
}(); err != nil {
err = &ogenerrors.DecodeBodyError{
ContentType: ct,
Body: buf,
Err: err,
}
return res, err
}
return &ErrorStatusCode{
StatusCode: resp.StatusCode,
Response: response,
}, nil
default:
return res, validate.InvalidContentType(ct)
}
}()
if err != nil {
return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode)
}
return res, errors.Wrap(defRes, "error")
}
func decodeListSubmissionsResponse(resp *http.Response) (res []Submission, _ error) { func decodeListSubmissionsResponse(resp *http.Response) (res []Submission, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 200: case 200:

View File

@ -181,6 +181,24 @@ func encodeGetSubmissionResponse(response *Submission, w http.ResponseWriter, sp
return nil return nil
} }
func encodeListScriptPolicyResponse(response []ScriptPolicy, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
span.SetStatus(codes.Ok, http.StatusText(200))
e := new(jx.Encoder)
e.ArrStart()
for _, elem := range response {
elem.Encode(e)
}
e.ArrEnd()
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
return nil
}
func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter, span trace.Span) error { func encodeListSubmissionsResponse(response []Submission, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200) w.WriteHeader(200)

View File

@ -83,10 +83,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if len(elem) == 0 { if len(elem) == 0 {
switch r.Method { switch r.Method {
case "GET":
s.handleListScriptPolicyRequest([0]string{}, elemIsEscaped, w, r)
case "POST": case "POST":
s.handleCreateScriptPolicyRequest([0]string{}, elemIsEscaped, w, r) s.handleCreateScriptPolicyRequest([0]string{}, elemIsEscaped, w, r)
default: default:
s.notAllowed(w, r, "POST") s.notAllowed(w, r, "GET,POST")
} }
return return
@ -714,6 +716,14 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
if len(elem) == 0 { if len(elem) == 0 {
switch method { switch method {
case "GET":
r.name = ListScriptPolicyOperation
r.summary = "Get list of script policies"
r.operationID = "listScriptPolicy"
r.pathPattern = "/script-policy"
r.args = args
r.count = 0
return r, true
case "POST": case "POST":
r.name = CreateScriptPolicyOperation r.name = CreateScriptPolicyOperation
r.summary = "Create a new script policy" r.summary = "Create a new script policy"

View File

@ -122,6 +122,56 @@ func (s *ID) SetID(val int64) {
s.ID = val s.ID = val
} }
type ListScriptPolicyReq struct {
Page Pagination `json:"Page"`
Filter OptScriptPolicyFilter `json:"Filter"`
}
// GetPage returns the value of Page.
func (s *ListScriptPolicyReq) GetPage() Pagination {
return s.Page
}
// GetFilter returns the value of Filter.
func (s *ListScriptPolicyReq) GetFilter() OptScriptPolicyFilter {
return s.Filter
}
// SetPage sets the value of Page.
func (s *ListScriptPolicyReq) SetPage(val Pagination) {
s.Page = val
}
// SetFilter sets the value of Filter.
func (s *ListScriptPolicyReq) SetFilter(val OptScriptPolicyFilter) {
s.Filter = val
}
type ListSubmissionsReq struct {
Page Pagination `json:"Page"`
Filter OptSubmissionFilter `json:"Filter"`
}
// GetPage returns the value of Page.
func (s *ListSubmissionsReq) GetPage() Pagination {
return s.Page
}
// GetFilter returns the value of Filter.
func (s *ListSubmissionsReq) GetFilter() OptSubmissionFilter {
return s.Filter
}
// SetPage sets the value of Page.
func (s *ListSubmissionsReq) SetPage(val Pagination) {
s.Page = val
}
// SetFilter sets the value of Filter.
func (s *ListSubmissionsReq) SetFilter(val OptSubmissionFilter) {
s.Filter = val
}
// NewOptInt32 returns new OptInt32 with value set to v. // NewOptInt32 returns new OptInt32 with value set to v.
func NewOptInt32(v int32) OptInt32 { func NewOptInt32(v int32) OptInt32 {
return OptInt32{ return OptInt32{
@ -214,6 +264,52 @@ func (o OptInt64) Or(d int64) int64 {
return d return d
} }
// NewOptScriptPolicyFilter returns new OptScriptPolicyFilter with value set to v.
func NewOptScriptPolicyFilter(v ScriptPolicyFilter) OptScriptPolicyFilter {
return OptScriptPolicyFilter{
Value: v,
Set: true,
}
}
// OptScriptPolicyFilter is optional ScriptPolicyFilter.
type OptScriptPolicyFilter struct {
Value ScriptPolicyFilter
Set bool
}
// IsSet returns true if OptScriptPolicyFilter was set.
func (o OptScriptPolicyFilter) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptScriptPolicyFilter) Reset() {
var v ScriptPolicyFilter
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptScriptPolicyFilter) SetTo(v ScriptPolicyFilter) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptScriptPolicyFilter) Get() (v ScriptPolicyFilter, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptScriptPolicyFilter) Or(d ScriptPolicyFilter) ScriptPolicyFilter {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptString returns new OptString with value set to v. // NewOptString returns new OptString with value set to v.
func NewOptString(v string) OptString { func NewOptString(v string) OptString {
return OptString{ return OptString{
@ -491,6 +587,54 @@ func (s *ScriptPolicyCreate) SetPolicy(val int32) {
s.Policy = val s.Policy = val
} }
// Ref: #/components/schemas/ScriptPolicyFilter
type ScriptPolicyFilter struct {
ID OptInt64 `json:"ID"`
FromScriptHash OptString `json:"FromScriptHash"`
ToScriptID OptInt64 `json:"ToScriptID"`
Policy OptInt32 `json:"Policy"`
}
// GetID returns the value of ID.
func (s *ScriptPolicyFilter) GetID() OptInt64 {
return s.ID
}
// GetFromScriptHash returns the value of FromScriptHash.
func (s *ScriptPolicyFilter) GetFromScriptHash() OptString {
return s.FromScriptHash
}
// GetToScriptID returns the value of ToScriptID.
func (s *ScriptPolicyFilter) GetToScriptID() OptInt64 {
return s.ToScriptID
}
// GetPolicy returns the value of Policy.
func (s *ScriptPolicyFilter) GetPolicy() OptInt32 {
return s.Policy
}
// SetID sets the value of ID.
func (s *ScriptPolicyFilter) SetID(val OptInt64) {
s.ID = val
}
// SetFromScriptHash sets the value of FromScriptHash.
func (s *ScriptPolicyFilter) SetFromScriptHash(val OptString) {
s.FromScriptHash = val
}
// SetToScriptID sets the value of ToScriptID.
func (s *ScriptPolicyFilter) SetToScriptID(val OptInt64) {
s.ToScriptID = val
}
// SetPolicy sets the value of Policy.
func (s *ScriptPolicyFilter) SetPolicy(val OptInt32) {
s.Policy = val
}
// Ref: #/components/schemas/ScriptPolicyUpdate // Ref: #/components/schemas/ScriptPolicyUpdate
type ScriptPolicyUpdate struct { type ScriptPolicyUpdate struct {
ID int64 `json:"ID"` ID int64 `json:"ID"`

View File

@ -110,12 +110,18 @@ type Handler interface {
// //
// GET /submissions/{SubmissionID} // GET /submissions/{SubmissionID}
GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error) GetSubmission(ctx context.Context, params GetSubmissionParams) (*Submission, error)
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
ListScriptPolicy(ctx context.Context, req *ListScriptPolicyReq) ([]ScriptPolicy, error)
// ListSubmissions implements listSubmissions operation. // ListSubmissions implements listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
ListSubmissions(ctx context.Context, params ListSubmissionsParams) ([]Submission, error) ListSubmissions(ctx context.Context, req *ListSubmissionsReq) ([]Submission, error)
// SetSubmissionCompleted implements setSubmissionCompleted operation. // SetSubmissionCompleted implements setSubmissionCompleted operation.
// //
// Retrieve map with ID. // Retrieve map with ID.

View File

@ -166,12 +166,21 @@ func (UnimplementedHandler) GetSubmission(ctx context.Context, params GetSubmiss
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (UnimplementedHandler) ListScriptPolicy(ctx context.Context, req *ListScriptPolicyReq) (r []ScriptPolicy, _ error) {
return r, ht.ErrNotImplemented
}
// ListSubmissions implements listSubmissions operation. // ListSubmissions implements listSubmissions operation.
// //
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
func (UnimplementedHandler) ListSubmissions(ctx context.Context, params ListSubmissionsParams) (r []Submission, _ error) { func (UnimplementedHandler) ListSubmissions(ctx context.Context, req *ListSubmissionsReq) (r []Submission, _ error) {
return r, ht.ErrNotImplemented return r, ht.ErrNotImplemented
} }

View File

@ -1,305 +0,0 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"math/bits"
"strconv"
"github.com/go-faster/errors"
"github.com/ogen-go/ogen/conv"
"github.com/ogen-go/ogen/uri"
"github.com/ogen-go/ogen/validate"
)
// EncodeURI encodes Pagination as URI form.
func (s *Pagination) EncodeURI(e uri.Encoder) error {
if err := e.EncodeField("Page", func(e uri.Encoder) error {
return e.EncodeValue(conv.Int32ToString(s.Page))
}); err != nil {
return errors.Wrap(err, "encode field \"Page\"")
}
if err := e.EncodeField("Limit", func(e uri.Encoder) error {
return e.EncodeValue(conv.Int32ToString(s.Limit))
}); err != nil {
return errors.Wrap(err, "encode field \"Limit\"")
}
return nil
}
var uriFieldsNameOfPagination = [2]string{
0: "Page",
1: "Limit",
}
// DecodeURI decodes Pagination from URI form.
func (s *Pagination) DecodeURI(d uri.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Pagination to nil")
}
var requiredBitSet [1]uint8
if err := d.DecodeFields(func(k string, d uri.Decoder) error {
switch k {
case "Page":
requiredBitSet[0] |= 1 << 0
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt32(val)
if err != nil {
return err
}
s.Page = c
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Page\"")
}
case "Limit":
requiredBitSet[0] |= 1 << 1
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt32(val)
if err != nil {
return err
}
s.Limit = c
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Limit\"")
}
default:
return nil
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Pagination")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000011,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(uriFieldsNameOfPagination) {
name = uriFieldsNameOfPagination[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
// EncodeURI encodes SubmissionFilter as URI form.
func (s *SubmissionFilter) EncodeURI(e uri.Encoder) error {
if err := e.EncodeField("ID", func(e uri.Encoder) error {
return e.EncodeValue(conv.Int64ToString(s.ID))
}); err != nil {
return errors.Wrap(err, "encode field \"ID\"")
}
if err := e.EncodeField("DisplayName", func(e uri.Encoder) error {
if val, ok := s.DisplayName.Get(); ok {
return e.EncodeValue(conv.StringToString(val))
}
return nil
}); err != nil {
return errors.Wrap(err, "encode field \"DisplayName\"")
}
if err := e.EncodeField("Creator", func(e uri.Encoder) error {
if val, ok := s.Creator.Get(); ok {
return e.EncodeValue(conv.StringToString(val))
}
return nil
}); err != nil {
return errors.Wrap(err, "encode field \"Creator\"")
}
if err := e.EncodeField("GameID", func(e uri.Encoder) error {
if val, ok := s.GameID.Get(); ok {
return e.EncodeValue(conv.Int32ToString(val))
}
return nil
}); err != nil {
return errors.Wrap(err, "encode field \"GameID\"")
}
return nil
}
var uriFieldsNameOfSubmissionFilter = [4]string{
0: "ID",
1: "DisplayName",
2: "Creator",
3: "GameID",
}
// DecodeURI decodes SubmissionFilter from URI form.
func (s *SubmissionFilter) DecodeURI(d uri.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode SubmissionFilter to nil")
}
var requiredBitSet [1]uint8
if err := d.DecodeFields(func(k string, d uri.Decoder) error {
switch k {
case "ID":
requiredBitSet[0] |= 1 << 0
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
s.ID = c
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"ID\"")
}
case "DisplayName":
if err := func() error {
var sDotDisplayNameVal string
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
sDotDisplayNameVal = c
return nil
}(); err != nil {
return err
}
s.DisplayName.SetTo(sDotDisplayNameVal)
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"DisplayName\"")
}
case "Creator":
if err := func() error {
var sDotCreatorVal string
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToString(val)
if err != nil {
return err
}
sDotCreatorVal = c
return nil
}(); err != nil {
return err
}
s.Creator.SetTo(sDotCreatorVal)
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"Creator\"")
}
case "GameID":
if err := func() error {
var sDotGameIDVal int32
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt32(val)
if err != nil {
return err
}
sDotGameIDVal = c
return nil
}(); err != nil {
return err
}
s.GameID.SetTo(sDotGameIDVal)
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"GameID\"")
}
default:
return nil
}
return nil
}); err != nil {
return errors.Wrap(err, "decode SubmissionFilter")
}
// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(uriFieldsNameOfSubmissionFilter) {
name = uriFieldsNameOfSubmissionFilter[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}

View File

@ -8,6 +8,88 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
func (s *ListScriptPolicyReq) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Page.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Page",
Error: err,
})
}
if err := func() error {
if value, ok := s.Filter.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Filter",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ListSubmissionsReq) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if err := s.Page.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Page",
Error: err,
})
}
if err := func() error {
if value, ok := s.Filter.Get(); ok {
if err := func() error {
if err := value.Validate(); err != nil {
return err
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "Filter",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *Pagination) Validate() error { func (s *Pagination) Validate() error {
if s == nil { if s == nil {
return validate.ErrNilPointer return validate.ErrNilPointer
@ -172,6 +254,44 @@ func (s *ScriptPolicy) Validate() error {
return nil return nil
} }
func (s *ScriptPolicyFilter) Validate() error {
if s == nil {
return validate.ErrNilPointer
}
var failures []validate.FieldError
if err := func() error {
if value, ok := s.FromScriptHash.Get(); ok {
if err := func() error {
if err := (validate.String{
MinLength: 16,
MinLengthSet: true,
MaxLength: 16,
MaxLengthSet: true,
Email: false,
Hostname: false,
Regex: nil,
}).Validate(string(value)); err != nil {
return errors.Wrap(err, "string")
}
return nil
}(); err != nil {
return err
}
}
return nil
}(); err != nil {
failures = append(failures, validate.FieldError{
Name: "FromScriptHash",
Error: err,
})
}
if len(failures) > 0 {
return &validate.Error{Fields: failures}
}
return nil
}
func (s *ScriptUpdate) Validate() error { func (s *ScriptUpdate) Validate() error {
if s == nil { if s == nil {
return validate.ErrNilPointer return validate.ErrNilPointer

View File

@ -41,4 +41,5 @@ type ScriptPolicy interface {
Create(ctx context.Context, smap model.ScriptPolicy) (model.ScriptPolicy, error) Create(ctx context.Context, smap model.ScriptPolicy) (model.ScriptPolicy, error)
Update(ctx context.Context, id int64, values OptionalMap) error Update(ctx context.Context, id int64, values OptionalMap) error
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
List(ctx context.Context, filters OptionalMap, page model.Page) ([]model.ScriptPolicy, error)
} }

View File

@ -62,3 +62,12 @@ func (env *ScriptPolicy) Delete(ctx context.Context, id int64) error {
return nil return nil
} }
func (env *ScriptPolicy) List(ctx context.Context, filters datastore.OptionalMap, page model.Page) ([]model.ScriptPolicy, error) {
var maps []model.ScriptPolicy
if err := env.db.Where(filters.Map()).Offset(int((page.Number - 1) * page.Size)).Limit(int(page.Size)).Find(&maps).Error; err != nil {
return nil, err
}
return maps, nil
}

View File

@ -5,10 +5,11 @@ import "time"
type Policy int32 type Policy int32
const ( const (
ScriptPolicyAllowed Policy = 0 ScriptPolicyNone Policy = 0 // not yet reviewed
ScriptPolicyBlocked Policy = 1 ScriptPolicyAllowed Policy = 1
ScriptPolicyDelete Policy = 2 ScriptPolicyBlocked Policy = 2
ScriptPolicyReplace Policy = 3 ScriptPolicyDelete Policy = 3
ScriptPolicyReplace Policy = 4
) )
type ScriptPolicy struct { type ScriptPolicy struct {

View File

@ -47,6 +47,42 @@ func (svc *Service) CreateScriptPolicy(ctx context.Context, req *api.ScriptPolic
}, nil }, nil
} }
// ListScriptPolicy implements listScriptPolicy operation.
//
// Get list of script policies.
//
// GET /script-policy
func (svc *Service) ListScriptPolicy(ctx context.Context, request *api.ListScriptPolicyReq) ([]api.ScriptPolicy, error) {
filter := datastore.Optional()
//fmt.Println(request)
if request.Filter.IsSet() {
filter.AddNotNil("from_script_hash", request.Filter.Value.FromScriptHash)
filter.AddNotNil("to_script_id", request.Filter.Value.ToScriptID)
filter.AddNotNil("policy", request.Filter.Value.Policy)
}
items, err := svc.DB.ScriptPolicy().List(ctx, filter, model.Page{
Number: request.Page.GetPage(),
Size: request.Page.GetLimit(),
})
if err != nil {
return nil, err
}
var resp []api.ScriptPolicy
for i := 0; i < len(items); i++ {
resp = append(resp, api.ScriptPolicy{
ID: items[i].ID,
FromScriptHash: fmt.Sprintf("%x", items[i].FromScriptHash),
ToScriptID: items[i].ToScriptID,
Policy: int32(items[i].Policy),
})
}
return resp, nil
}
// DeleteScriptPolicy implements deleteScriptPolicy operation. // DeleteScriptPolicy implements deleteScriptPolicy operation.
// //
// Delete the specified script policy by ID. // Delete the specified script policy by ID.

View File

@ -67,7 +67,7 @@ func (svc *Service) GetSubmission(ctx context.Context, params api.GetSubmissionP
// Get list of submissions. // Get list of submissions.
// //
// GET /submissions // GET /submissions
func (svc *Service) ListSubmissions(ctx context.Context, request api.ListSubmissionsParams) ([]api.Submission, error) { func (svc *Service) ListSubmissions(ctx context.Context, request *api.ListSubmissionsReq) ([]api.Submission, error) {
filter := datastore.Optional() filter := datastore.Optional()
//fmt.Println(request) //fmt.Println(request)
if request.Filter.IsSet() { if request.Filter.IsSet() {

View File

@ -0,0 +1,2 @@
[registries.strafesnet]
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"

1
releaser/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1979
releaser/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
releaser/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "releaser"
version = "0.1.0"
edition = "2021"
[dependencies]
submissions-api = { version = "0.1.0", registry = "strafesnet" }
rbx_asset = { version = "0.2.5", registry = "strafesnet" }
rust-grpc = { version = "1.0.3", registry = "strafesnet" }
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
tonic = "0.12.3"

24
releaser/Containerfile Normal file
View File

@ -0,0 +1,24 @@
# Using the `rust-musl-builder` as base image, instead of
# the official Rust toolchain
FROM docker.io/clux/muslrust:stable AS chef
USER root
RUN cargo install cargo-chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# Notice that we are specifying the --target flag!
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl --bin releaser
FROM docker.io/alpine:latest AS runtime
RUN addgroup -S myuser && adduser -S myuser -G myuser
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/releaser /usr/local/bin/
USER myuser
ENTRYPOINT ["/usr/local/bin/releaser"]

32
releaser/src/main.rs Normal file
View File

@ -0,0 +1,32 @@
#[allow(dead_code)]
#[derive(Debug)]
pub enum StartupError{
API(submissions_api::ReqwestError),
GRPCConnect(tonic::transport::Error),
}
impl std::fmt::Display for StartupError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for StartupError{}
// annoying mile-long type
pub type MapsServiceClient=rust_grpc::maps::maps_service_client::MapsServiceClient<tonic::transport::channel::Channel>;
#[tokio::main]
async fn main()->Result<(),StartupError>{
// maps-service api
let api_host=std::env::var("API_HOST").expect("API_HOST env required");
let api=submissions_api::Context::new(api_host).map_err(StartupError::API)?;
// data-service grpc for creating map entries
let data_host=std::env::var("DATA_HOST").expect("DATA_HOST env required");
let maps_grpc=crate::MapsServiceClient::connect(data_host).await.map_err(StartupError::GRPCConnect)?;
// request maps pending release
// randomize list
// release maps
Ok(())
}

1
submissions-api-rs/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1451
submissions-api-rs/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[package] [package]
name = "api" name = "submissions-api"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = ["strafesnet"] publish = ["strafesnet"]

View File

@ -29,10 +29,11 @@ pub struct ScriptResponse{
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
#[repr(i32)] #[repr(i32)]
pub enum Policy{ pub enum Policy{
Allowed=0, None=0, // not yet reviewed
Blocked=1, Allowed=1,
Delete=2, Blocked=2,
Replace=3, Delete=3,
Replace=4,
} }
pub struct ScriptPolicyHashRequest{ pub struct ScriptPolicyHashRequest{

306
validation/Cargo.lock generated
View File

@ -41,22 +41,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anyhow"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]]
name = "api"
version = "0.1.0"
dependencies = [
"reqwest",
"serde",
"serde_json",
"url",
]
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.9" version = "0.3.9"
@ -105,39 +89,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "async-stream"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -150,53 +101,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "axum"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower 0.5.2",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.74" version = "0.3.74"
@ -477,12 +381,6 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -688,19 +586,13 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http", "http",
"indexmap 2.7.0", "indexmap",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing", "tracing",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.2"
@ -747,12 +639,6 @@ version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.5.1" version = "1.5.1"
@ -766,7 +652,6 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"smallvec", "smallvec",
@ -791,19 +676,6 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.6.0" version = "0.6.0"
@ -1001,16 +873,6 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.0" version = "2.7.0"
@ -1018,7 +880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.2", "hashbrown",
] ]
[[package]] [[package]]
@ -1027,15 +889,6 @@ version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.14"
@ -1105,7 +958,6 @@ dependencies = [
name = "maps-validation" name = "maps-validation"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"api",
"async-nats", "async-nats",
"futures", "futures",
"rbx_asset", "rbx_asset",
@ -1113,20 +965,13 @@ dependencies = [
"rbx_dom_weak", "rbx_dom_weak",
"rbx_reflection_database", "rbx_reflection_database",
"rbx_xml", "rbx_xml",
"rust-grpc",
"serde", "serde",
"serde_json", "serde_json",
"siphasher", "siphasher",
"submissions-api",
"tokio", "tokio",
"tonic",
] ]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -1402,40 +1247,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "prost"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "prost-types"
version = "0.13.3-serde1"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "8819b8aaed4d56560ff473ee45247539098b6fba5c8748a429903c499d5bc4a9"
dependencies = [
"prost",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.37"
@ -1676,18 +1487,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "rust-grpc"
version = "1.0.3"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "9d241d8d3ce1f24ce54ead69478676d138526bc0d44bb51bea2d87e5d8e3af22"
dependencies = [
"prost",
"prost-types",
"serde",
"tonic",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@ -1781,12 +1580,6 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rustversion"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.18"
@ -2009,6 +1802,18 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "submissions-api"
version = "0.1.0"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "7969c6c0b770ecfb57220c340f732d62a6fb1165b9ef038e33e7cd5a9658bdd5"
dependencies = [
"reqwest",
"serde",
"serde_json",
"url",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@ -2189,17 +1994,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.13" version = "0.7.13"
@ -2234,76 +2028,6 @@ dependencies = [
"tokio-util", "tokio-util",
] ]
[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum",
"base64 0.22.1",
"bytes",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"prost",
"socket2",
"tokio",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.3" version = "0.3.3"

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
api = { path = "api" } submissions-api = { version = "0.1.0", registry = "strafesnet" }
async-nats = "0.38.0" async-nats = "0.38.0"
futures = "0.3.31" futures = "0.3.31"
rbx_asset = { version = "0.2.5", registry = "strafesnet" } rbx_asset = { version = "0.2.5", registry = "strafesnet" }
@ -12,9 +12,7 @@ rbx_binary = { version = "0.7.4", registry = "strafesnet"}
rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"} rbx_dom_weak = { version = "2.9.0", registry = "strafesnet"}
rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"} rbx_reflection_database = { version = "0.2.12", registry = "strafesnet"}
rbx_xml = { version = "0.13.3", registry = "strafesnet"} rbx_xml = { version = "0.13.3", registry = "strafesnet"}
rust-grpc = { version = "1.0.3", registry = "strafesnet" }
serde = { version = "1.0.215", features = ["derive"] } serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133" serde_json = "1.0.133"
siphasher = "1.0.1" siphasher = "1.0.1"
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "fs", "signal"] } tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
tonic = "0.12.3"

View File

@ -11,7 +11,7 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json COPY --from=planner /app/recipe.json recipe.json
COPY api ./api
# Notice that we are specifying the --target flag! # Notice that we are specifying the --target flag!
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . . COPY . .

View File

@ -9,12 +9,11 @@ mod message_handler;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum StartupError{ pub enum StartupError{
API(api::ReqwestError), API(submissions_api::ReqwestError),
NatsConnect(async_nats::ConnectError), NatsConnect(async_nats::ConnectError),
NatsGetStream(async_nats::jetstream::context::GetStreamError), NatsGetStream(async_nats::jetstream::context::GetStreamError),
NatsConsumer(async_nats::jetstream::stream::ConsumerError), NatsConsumer(async_nats::jetstream::stream::ConsumerError),
NatsStream(async_nats::jetstream::consumer::StreamError), NatsStream(async_nats::jetstream::consumer::StreamError),
GRPCConnect(tonic::transport::Error),
} }
impl std::fmt::Display for StartupError{ impl std::fmt::Display for StartupError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -23,9 +22,6 @@ impl std::fmt::Display for StartupError{
} }
impl std::error::Error for StartupError{} impl std::error::Error for StartupError{}
// annoying mile-long type
pub type MapsServiceClient=rust_grpc::maps::maps_service_client::MapsServiceClient<tonic::transport::channel::Channel>;
pub const GROUP_STRAFESNET:u64=6980477; pub const GROUP_STRAFESNET:u64=6980477;
pub const PARALLEL_REQUESTS:usize=16; pub const PARALLEL_REQUESTS:usize=16;
@ -38,7 +34,7 @@ async fn main()->Result<(),StartupError>{
// maps-service api // maps-service api
let api_host=std::env::var("API_HOST").expect("API_HOST env required"); let api_host=std::env::var("API_HOST").expect("API_HOST env required");
let api=api::Context::new(api_host).map_err(StartupError::API)?; let api=submissions_api::Context::new(api_host).map_err(StartupError::API)?;
// nats // nats
let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required"); let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required");
@ -56,18 +52,13 @@ async fn main()->Result<(),StartupError>{
.messages().await.map_err(StartupError::NatsStream) .messages().await.map_err(StartupError::NatsStream)
}; };
// data-service grpc for creating map entries let message_handler=message_handler::MessageHandler::new(cookie_context,api);
let data_host=std::env::var("DATA_HOST").expect("DATA_HOST env required");
let message_handler_fut=async{
let maps_grpc=crate::MapsServiceClient::connect(data_host).await.map_err(StartupError::GRPCConnect)?;
Ok(message_handler::MessageHandler::new(cookie_context,api,maps_grpc))
};
// Create a signal listener for SIGTERM // Create a signal listener for SIGTERM
let mut sig_term=tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).expect("Failed to create SIGTERM signal listener"); let mut sig_term=tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).expect("Failed to create SIGTERM signal listener");
// run futures // run futures
let (mut messages,message_handler)=tokio::try_join!(nats_fut,message_handler_fut)?; let mut messages=nats_fut.await?;
// process up to PARALLEL_REQUESTS in parallel // process up to PARALLEL_REQUESTS in parallel
let main_loop=async move{ let main_loop=async move{

View File

@ -26,11 +26,10 @@ pub struct MessageHandler{
impl MessageHandler{ impl MessageHandler{
pub fn new( pub fn new(
cookie_context:rbx_asset::cookie::CookieContext, cookie_context:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
maps_grpc:crate::MapsServiceClient,
)->Self{ )->Self{
Self{ Self{
publish_new:crate::publish_new::Publisher::new(cookie_context.clone(),api.clone(),maps_grpc), publish_new:crate::publish_new::Publisher::new(cookie_context.clone(),api.clone()),
publish_fix:crate::publish_fix::Publisher::new(cookie_context.clone(),api.clone()), publish_fix:crate::publish_fix::Publisher::new(cookie_context.clone(),api.clone()),
validator:crate::validator::Validator::new(cookie_context,api), validator:crate::validator::Validator::new(cookie_context,api),
} }

View File

@ -6,7 +6,7 @@ pub enum PublishError{
Get(rbx_asset::cookie::GetError), Get(rbx_asset::cookie::GetError),
Json(serde_json::Error), Json(serde_json::Error),
Upload(rbx_asset::cookie::UploadError), Upload(rbx_asset::cookie::UploadError),
ApiActionSubmissionPublish(api::Error), ApiActionSubmissionPublish(submissions_api::Error),
} }
impl std::fmt::Display for PublishError{ impl std::fmt::Display for PublishError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -17,12 +17,12 @@ impl std::error::Error for PublishError{}
pub struct Publisher{ pub struct Publisher{
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
} }
impl Publisher{ impl Publisher{
pub const fn new( pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
)->Self{ )->Self{
Self{ Self{
roblox_cookie, roblox_cookie,
@ -54,7 +54,7 @@ impl Publisher{
// mark submission as published // mark submission as published
self.api.action_submission_publish( self.api.action_submission_publish(
api::SubmissionID(publish_info.SubmissionID) submissions_api::SubmissionID(publish_info.SubmissionID)
).await.map_err(PublishError::ApiActionSubmissionPublish)?; ).await.map_err(PublishError::ApiActionSubmissionPublish)?;
Ok(()) Ok(())

View File

@ -7,8 +7,7 @@ pub enum PublishError{
Json(serde_json::Error), Json(serde_json::Error),
Create(rbx_asset::cookie::CreateError), Create(rbx_asset::cookie::CreateError),
SystemTime(std::time::SystemTimeError), SystemTime(std::time::SystemTimeError),
MapCreate(tonic::Status), ApiActionSubmissionPublish(submissions_api::Error),
ApiActionSubmissionPublish(api::Error),
} }
impl std::fmt::Display for PublishError{ impl std::fmt::Display for PublishError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -19,19 +18,16 @@ impl std::error::Error for PublishError{}
pub struct Publisher{ pub struct Publisher{
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
maps_grpc:crate::MapsServiceClient,
} }
impl Publisher{ impl Publisher{
pub const fn new( pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
maps_grpc:crate::MapsServiceClient,
)->Self{ )->Self{
Self{ Self{
roblox_cookie, roblox_cookie,
api, api,
maps_grpc,
} }
} }
pub async fn publish(&self,message:async_nats::jetstream::Message)->Result<(),PublishError>{ pub async fn publish(&self,message:async_nats::jetstream::Message)->Result<(),PublishError>{
@ -54,28 +50,9 @@ impl Publisher{
groupId:Some(crate::GROUP_STRAFESNET), groupId:Some(crate::GROUP_STRAFESNET),
},model_data).await.map_err(PublishError::Create)?; },model_data).await.map_err(PublishError::Create)?;
// create the map entry in the game database, including release date
self.maps_grpc.clone().create(rust_grpc::maps::MapRequest{
id:upload_response.AssetId as i64,
display_name:Some(publish_info.DisplayName),
creator:Some(publish_info.Creator),
game_id:Some(publish_info.GameID as i32),
// TODO: scheduling algorithm
date:Some(
// Publish one week from now
(
std::time::SystemTime::now()
+std::time::Duration::from_secs(7*24*60*60)
)
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map_err(PublishError::SystemTime)?
.as_secs() as i64
),
}).await.map_err(PublishError::MapCreate)?;
// mark submission as published // mark submission as published
self.api.action_submission_publish( self.api.action_submission_publish(
api::SubmissionID(publish_info.SubmissionID) submissions_api::SubmissionID(publish_info.SubmissionID)
).await.map_err(PublishError::ApiActionSubmissionPublish)?; ).await.map_err(PublishError::ApiActionSubmissionPublish)?;
Ok(()) Ok(())

View File

@ -5,6 +5,7 @@ use crate::nats_types::ValidateRequest;
const SCRIPT_CONCURRENCY:usize=16; const SCRIPT_CONCURRENCY:usize=16;
enum Policy{ enum Policy{
None,
Allowed, Allowed,
Blocked, Blocked,
Delete, Delete,
@ -19,10 +20,10 @@ pub enum ValidateError{
Get(rbx_asset::cookie::GetError), Get(rbx_asset::cookie::GetError),
Json(serde_json::Error), Json(serde_json::Error),
ReadDom(ReadDomError), ReadDom(ReadDomError),
ApiGetScriptPolicy(api::Error), ApiGetScriptPolicy(submissions_api::Error),
ApiGetScript(api::Error), ApiGetScript(submissions_api::Error),
ApiUpdateSubmissionModel(api::Error), ApiUpdateSubmissionModel(submissions_api::Error),
ApiActionSubmissionValidate(api::Error), ApiActionSubmissionValidate(submissions_api::Error),
WriteDom(rbx_binary::EncodeError), WriteDom(rbx_binary::EncodeError),
Upload(rbx_asset::cookie::UploadError), Upload(rbx_asset::cookie::UploadError),
Create(rbx_asset::cookie::CreateError), Create(rbx_asset::cookie::CreateError),
@ -36,13 +37,13 @@ impl std::error::Error for ValidateError{}
pub struct Validator{ pub struct Validator{
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
} }
impl Validator{ impl Validator{
pub const fn new( pub const fn new(
roblox_cookie:rbx_asset::cookie::CookieContext, roblox_cookie:rbx_asset::cookie::CookieContext,
api:api::Context, api:submissions_api::Context,
)->Self{ )->Self{
Self{ Self{
roblox_cookie, roblox_cookie,
@ -71,7 +72,7 @@ impl Validator{
for &script_ref in &script_refs{ for &script_ref in &script_refs{
if let Some(script)=dom.get_by_ref(script_ref){ if let Some(script)=dom.get_by_ref(script_ref){
if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get("Source"){ if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get("Source"){
script_map.insert(source.clone(),Policy::Blocked); script_map.insert(source.clone(),Policy::None);
} }
} }
} }
@ -85,17 +86,18 @@ impl Validator{
let hash=std::hash::Hasher::finish(&hasher); let hash=std::hash::Hasher::finish(&hasher);
// fetch the script policy // fetch the script policy
let script_policy=self.api.get_script_policy_from_hash(api::ScriptPolicyHashRequest{ let script_policy=self.api.get_script_policy_from_hash(submissions_api::ScriptPolicyHashRequest{
hash:format!("{:x}",hash), hash:format!("{:x}",hash),
}).await.map_err(ValidateError::ApiGetScriptPolicy)?; }).await.map_err(ValidateError::ApiGetScriptPolicy)?;
// write the policy to the script_map, fetching the replacement code if necessary // write the policy to the script_map, fetching the replacement code if necessary
*replacement=match script_policy.Policy{ *replacement=match script_policy.Policy{
api::Policy::Allowed=>Policy::Allowed, submissions_api::Policy::None=>Policy::None,
api::Policy::Blocked=>Policy::Blocked, submissions_api::Policy::Allowed=>Policy::Allowed,
api::Policy::Delete=>Policy::Delete, submissions_api::Policy::Blocked=>Policy::Blocked,
api::Policy::Replace=>{ submissions_api::Policy::Delete=>Policy::Delete,
let script=self.api.get_script(api::GetScriptRequest{ submissions_api::Policy::Replace=>{
let script=self.api.get_script(submissions_api::GetScriptRequest{
ScriptID:script_policy.ToScriptID, ScriptID:script_policy.ToScriptID,
}).await.map_err(ValidateError::ApiGetScript)?; }).await.map_err(ValidateError::ApiGetScript)?;
Policy::Replace(script.Source) Policy::Replace(script.Source)
@ -113,7 +115,9 @@ impl Validator{
if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){ if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){
match script_map.get(source.as_str()){ match script_map.get(source.as_str()){
Some(Policy::Blocked)=>return Err(ValidateError::Blocked), Some(Policy::Blocked)=>return Err(ValidateError::Blocked),
None=>return Err(ValidateError::NotAllowed), None
|Some(Policy::None)
=>return Err(ValidateError::NotAllowed),
Some(Policy::Allowed)=>(), Some(Policy::Allowed)=>(),
Some(Policy::Delete)=>{ Some(Policy::Delete)=>{
modified=true; modified=true;
@ -162,7 +166,7 @@ impl Validator{
}; };
// update the submission to use the validated model // update the submission to use the validated model
self.api.update_submission_model(api::UpdateSubmissionModelRequest{ self.api.update_submission_model(submissions_api::UpdateSubmissionModelRequest{
ID:validate_info.SubmissionID, ID:validate_info.SubmissionID,
ModelID:model_id, ModelID:model_id,
ModelVersion:1, //TODO ModelVersion:1, //TODO
@ -171,7 +175,7 @@ impl Validator{
// update the submission model status to validated // update the submission model status to validated
self.api.action_submission_validate( self.api.action_submission_validate(
api::SubmissionID(validate_info.SubmissionID) submissions_api::SubmissionID(validate_info.SubmissionID)
).await.map_err(ValidateError::ApiActionSubmissionValidate)?; ).await.map_err(ValidateError::ApiActionSubmissionValidate)?;
Ok(()) Ok(())

View File

@ -1,4 +1,5 @@
$review-border: 1px solid var(--review-border); $review-border: 1px solid var(--review-border);
$form-label-fontsize: 1.3rem;
@mixin border-with-radius { @mixin border-with-radius {
border: $review-border { border: $review-border {
@ -21,6 +22,7 @@ $review-border: 1px solid var(--review-border);
--window-header: #f5f5f5; --window-header: #f5f5f5;
--comment-highlighted: #ffffd7; --comment-highlighted: #ffffd7;
--comment-area: white; --comment-area: white;
--placeholder-text: rgb(150,150,150);
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
--page: rgb(15,15,15); --page: rgb(15,15,15);
@ -35,6 +37,7 @@ $review-border: 1px solid var(--review-border);
--window-header: rgb(10,10,10); --window-header: rgb(10,10,10);
--comment-highlighted: #ffffd7; --comment-highlighted: #ffffd7;
--comment-area: rgb(20,20,20); --comment-area: rgb(20,20,20);
--placeholder-text: rgb(80,80,80);
} }
} }
@ -52,3 +55,10 @@ button {
a:active, a:link, a:hover { a:active, a:link, a:hover {
text-decoration: none; text-decoration: none;
} }
.spacer {
display: block;
width: 100%;
height: 1px;
background-color: var(--review-border);
}

View File

@ -14,13 +14,6 @@
width: 100vw; width: 100vw;
} }
.spacer {
display: block;
width: 100%;
height: 1px;
background-color: var(--review-border);
}
.by-creator { .by-creator {
margin-top: 10px; margin-top: 10px;
} }

View File

@ -1,24 +1,11 @@
"use client"
import { useState, useEffect } from "react"
import { SubmissionInfo } from "@/app/ts/Submission" import { SubmissionInfo } from "@/app/ts/Submission"
import { AssetImage } from "@/app/ts/Roblox"
import Image from "next/image"
interface AssetID { interface AssetID {
id: SubmissionInfo["AssetID"] id: SubmissionInfo["AssetID"]
} }
function MapImage(asset: AssetID) { function MapImage() {
const [assetImage, setAssetImage] = useState(""); return <p>Fetching map image...</p>
useEffect(() => {
AssetImage(asset.id, "420x420").then(image => setAssetImage(image))
}, [asset.id]);
if (!assetImage) {
return <p>Fetching map image...</p>;
}
return <Image src={assetImage} alt="Map Image"/>
} }
export { export {

View File

@ -1,7 +1,7 @@
import { Button, ButtonOwnProps } from "@mui/material"; import { Button, ButtonOwnProps } from "@mui/material";
type Review = "Completed" | "Submit" | "Reject" | "Revoke" | "Accept" | "Publish" type Review = "Completed" | "Submit" | "Reject" | "Revoke" | "Accept" | "Validate" | "Upload"
type Action = "completed" | "submit" | "reject" | "revoke" | "trigger-validate" | "trigger-publish" type Action = "completed" | "submit" | "reject" | "revoke" | "trigger-validate" | "trigger-upload"
interface ReviewButton { interface ReviewButton {
name: Review, name: Review,
action: Action, action: Action,
@ -9,7 +9,7 @@ interface ReviewButton {
} }
function ReviewButtonClicked(action: Action) { function ReviewButtonClicked(action: Action) {
const post = fetch(`http://localhost:3000/v1/submissions/1/status/${action}`, { fetch(`http://localhost:3000/v1/submissions/1/status/${action}`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-type": "application/json", "Content-type": "application/json",
@ -27,8 +27,9 @@ export default function ReviewButtons() {
<ReviewButton color="info" name="Submit" action="submit"/> <ReviewButton color="info" name="Submit" action="submit"/>
<ReviewButton color="info" name="Revoke" action="revoke"/> <ReviewButton color="info" name="Revoke" action="revoke"/>
<ReviewButton color="info" name="Accept" action="trigger-validate"/> <ReviewButton color="info" name="Accept" action="trigger-validate"/>
<ReviewButton color="info" name="Validate" action="trigger-validate"/>
<ReviewButton color="error" name="Reject" action="reject"/> <ReviewButton color="error" name="Reject" action="reject"/>
<ReviewButton color="info" name="Publish" action="trigger-publish"/> <ReviewButton color="info" name="Upload" action="trigger-upload"/>
<ReviewButton color="info" name="Completed" action="completed"/> <ReviewButton color="info" name="Completed" action="completed"/>
</section> </section>
) )

View File

@ -2,7 +2,7 @@
import { SubmissionStatus, SubmissionStatusToString } from "@/app/ts/Submission"; import { SubmissionStatus, SubmissionStatusToString } from "@/app/ts/Submission";
import type { CreatorAndReviewStatus } from "./_comments"; import type { CreatorAndReviewStatus } from "./_comments";
import { MapImage, type AssetID } from "./_map"; import { MapImage } from "./_map";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import ReviewButtons from "./_reviewButtons"; import ReviewButtons from "./_reviewButtons";
import { Rating } from "@mui/material"; import { Rating } from "@mui/material";
@ -34,11 +34,11 @@ function Ratings() {
) )
} }
function RatingArea(asset: AssetID) { function RatingArea() {
return ( return (
<aside className="review-area"> <aside className="review-area">
<section className="map-image-area"> <section className="map-image-area">
<MapImage id={asset.id}/> <MapImage/>
</section> </section>
<Ratings/> <Ratings/>
<ReviewButtons/> <ReviewButtons/>
@ -71,7 +71,7 @@ export default function SubmissionInfoPage() {
<Webpage> <Webpage>
<main className="map-page-main"> <main className="map-page-main">
<section className="review-section"> <section className="review-section">
<RatingArea id={432}/> <RatingArea/>
<TitleAndComments name={dynamicId.submissionId} creator="Quaternions" review={SubmissionStatus.Accepted} comments={[]}/> <TitleAndComments name={dynamicId.submissionId} creator="Quaternions" review={SubmissionStatus.Accepted} comments={[]}/>
</section> </section>
</main> </main>

View File

@ -0,0 +1,55 @@
@use "../../globals.scss";
::placeholder {
color: var(--placeholder-text)
}
.form-spacer {
margin-bottom: 20px;
&:last-of-type {
margin-top: 15px;
}
}
#target-asset-radio {
color: var(--text-color);
font-size: globals.$form-label-fontsize;
}
.form-field {
width: 850px;
& label, & input {
color: var(--text-color);
}
& fieldset {
border-color: rgb(100,100,100);
}
& span {
color: white;
}
}
main {
display: grid;
justify-content: center;
align-items: center;
margin-inline: auto;
width: 700px;
}
header h1 {
text-align: center;
color: var(--text-color);
}
form {
display: grid;
gap: 25px;
fieldset {
border: blue
}
}

View File

@ -0,0 +1,67 @@
interface FormNumbers {
readonly GameID: number,
readonly AssetID: number,
readonly AssetVersion: number
}
interface FormStrings {
readonly DisplayName: string,
readonly Creator: string,
}
function SubmitAPI(json: string) {
console.log(json)
}
function SafeNumbers(form: FormNumbers): Promise<FormNumbers> {
const form_number_values = [form.GameID, form.AssetID, form.AssetVersion]
return new Promise((resolve, reject) => {
form_number_values.forEach(v => {
if (!Number.isSafeInteger(v)) {
reject(`Form Value: ${v} was not a valid number`)
}
})
resolve(form)
})
}
function SafeStrings(form: FormStrings): Promise<FormStrings> {
const form_string_values = [form.DisplayName, form.Creator]
return new Promise((resolve, reject) => {
form_string_values.forEach(v => {
if (v.length>=128) {
reject(`Form value: ${v} is beyond the max 128 string limit`)
}
})
resolve(form)
})
}
export default function Submit() {
const form_numbers: FormNumbers = {
GameID: Number((document.getElementById("game-id") as HTMLInputElement).value),
AssetID: Number((document.getElementById("asset-id") as HTMLInputElement).value),
AssetVersion: Number((document.getElementById("asset-version") as HTMLInputElement).value),
}
const form_strings: FormStrings = {
DisplayName: (document.getElementById("display-name") as HTMLInputElement).value,
Creator: (document.getElementById("creator") as HTMLInputElement).value
}
const valid_numbers = SafeNumbers(form_numbers)
const valid_strings = SafeStrings(form_strings)
valid_strings.then(form_strings => {
valid_numbers.then(form_numbers => {
SubmitAPI(JSON.stringify({
DisplayName: form_strings.DisplayName,
Creator: form_strings.Creator,
GameID: form_numbers.GameID,
AssetID: form_numbers.AssetID,
AssetVersion: form_numbers.AssetVersion
}))
}).catch(e => console.log(e))
}).catch(e => console.log(e))
}

View File

@ -0,0 +1,46 @@
"use client"
import { FormControl, FormLabel, RadioGroup, FormControlLabel, Button, TextField } from "@mui/material"
import SendIcon from '@mui/icons-material/Send';
import Webpage from "@/app/_components/webpage"
import Submit from "./_submit";
import Radio from '@mui/material/Radio';
import "./(styles)/page.scss"
function TargetAsset() {
return <FormControl>
<FormLabel id="target-asset-radio">Target:</FormLabel>
<RadioGroup defaultValue="New" aria-labelledby="target-asset-radio" name="target-asset-radio">
<FormControlLabel value="New" control={<Radio/>} label="New" id="asset-new"/>
<FormControlLabel value="Fix" control={<Radio/>} label="Fix" id="asset-Fix"/>
</RadioGroup>
</FormControl>
}
export default function SubmissionInfoPage() {
return (
<Webpage>
<main>
<header>
<h1>Submit Asset</h1>
<span className="spacer form-spacer"></span>
</header>
<form>
<TextField className="form-field" id="display-name" label="Display Name" variant="outlined"/>
<TextField className="form-field" id="creator" label="Creator" variant="outlined"/>
<TextField className="form-field" id="game-id" label="Game ID" variant="outlined" type="number"/>
<TextField className="form-field" id="asset-id" label="Asset ID" variant="outlined" type="number"/>
<TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined" type="number"/>
<TargetAsset/>
</form>
<span className="spacer form-spacer"></span>
<Button variant="contained" startIcon={<SendIcon />} onClick={() => { Submit() }} sx={{
width: "400px",
height: "50px",
marginInline: "auto"
}}>Submit</Button>
</main>
</Webpage>
)
}

View File

@ -1,48 +0,0 @@
const FALLBACK_IMAGE = ""
type thumbsizes = "420" | "720"
type thumbsize<S extends thumbsizes> = `${S}x${S}`
type ParsedJson<A> = {
errors: A,
data: {
[0]: {
state: string,
imageUrl: string,
}
}
}
function Parse<A>(json: ParsedJson<A>): string {
if (json.errors) {
console.warn(json.errors)
return FALLBACK_IMAGE
}
if (json.data) {
const data = json.data[0]
if (!data) { //For whatever reason roblox will sometimes return an empty array instead of an error message
console.warn("Roblox gave us no data,", data)
return FALLBACK_IMAGE
}
if (data.state === "Completed") {
return data.imageUrl
}
console.warn(data)
return FALLBACK_IMAGE
}
return FALLBACK_IMAGE
}
async function AvatarHeadshot<S extends thumbsizes>(userid: number, size: thumbsize<S>): Promise<string> {
const avatarthumb_api = fetch(`https://thumbnails.roproxy.com/v1/users/avatar-headshot?userIds=${userid}&size=${size}&format=Png&isCircular=false`)
return avatarthumb_api.then(res => res.json()).then(json => Parse(json))
}
async function AssetImage<S extends thumbsizes>(assetid: number, size: thumbsize<S>): Promise<string> {
const avatarthumb_api = fetch(`https://thumbnails.roblox.com/v1/assets?assetIds=${assetid}&returnPolicy=PlaceHolder&size=${size}&format=Png&isCircular=false`)
return avatarthumb_api.then(res => res.json()).then(json => Parse(json))
}
export {
AvatarHeadshot,
AssetImage
}