Compare commits

...

27 Commits

Author SHA1 Message Date
54caab67d2 releaser: run me to batch release maps
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-15 01:41:19 -08:00
ce59d7c947 submissions-api: v0.2.1 feature flag for external 2024-12-15 01:41:13 -08:00
ed68af80b0 docker: rename stuff for no reason 2024-12-15 01:41:13 -08:00
11846c32e6 docker: use env file because env var is broken 2024-12-15 01:05:51 -08:00
ecbd102aef docker: split internal & external api 2024-12-15 01:05:51 -08:00
cf1fdb4099 submissions-api-rs: v0.2.0 split internal & external 2024-12-15 01:05:51 -08:00
33d272ab04 nats: edit PublishNewRequest message 2024-12-15 01:05:51 -08:00
75d8cafc7b web: change buttons 2024-12-15 01:05:51 -08:00
7d2147779a submissions-api: v0.1.1 2024-12-15 01:05:51 -08:00
7e940cdfb1 submissions: update ActionSubmissionUploaded 2024-12-15 01:05:51 -08:00
47c30ad2db openapi-internal: optionally change TargetAssetID on upload 2024-12-15 01:05:51 -08:00
29b77f14de roles: potential future roles 2024-12-15 01:05:51 -08:00
9e022ca265 submissions: refactor publishing model 2024-12-15 01:05:51 -08:00
95675c51e6 openapi: generate 2024-12-15 00:07:09 -08:00
7a30dc4ec3 openapi: public endpoints use cookieAuth by default 2024-12-15 00:07:01 -08:00
cd9bb17370 openapi: move internal functions to separate api spec 2024-12-15 00:07:01 -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
83 changed files with 8453 additions and 2187 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 submissions:
maps-service: DOCKER_BUILDKIT=1 docker build . -f Containerfile -t maps-service-submissions
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: submissions web validation
.PHONY: submissions web validation

View File

@ -11,15 +11,17 @@ services:
networks: networks:
- maps-service-network - maps-service-network
mapsservice: submissions:
image: image:
maps-service maps-service-submissions
container_name: mapsservice container_name: submissions
command: [ command: [
# debug # debug
"--debug","serve", "--debug","serve",
# http service port # http service port
"--port","8082", "--port","8082",
# internal http endpoints
"--port-internal","8083",
# postgres # postgres
"--pg-host","10.0.0.29", "--pg-host","10.0.0.29",
"--pg-port","5432", "--pg-port","5432",
@ -50,16 +52,18 @@ services:
image: image:
maps-service-validation maps-service-validation
container_name: validation container_name: validation
env_file:
- ../auth-compose/strafesnet.env
environment: environment:
- RBXCOOKIE - API_HOST=http://submissions:8082
- API_HOST=http://mapsservice:8082 - API_HOST_INTERNAL=http://submissions:8083
- NATS_HOST=nats:4222 - NATS_HOST=nats:4222
- DATA_HOST=http://dataservice:9000 - DATA_HOST=http://dataservice:9000
depends_on: depends_on:
- nats - nats
# note: this races the mapsservice which creates a nats stream # note: this races the submissions which creates a nats stream
# the validation will panic if the nats stream is not created # the validation will panic if the nats stream is not created
- mapsservice - submissions
- dataservice - dataservice
networks: networks:
- maps-service-network - maps-service-network

View File

@ -1,3 +1,4 @@
package main package main
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/api --clean openapi.yaml //go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/api --clean openapi.yaml
//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/internal --clean openapi-internal.yaml

103
openapi-internal.yaml Normal file
View File

@ -0,0 +1,103 @@
openapi: 3.1.0
info:
title: StrafesNET Internal - OpenAPI 3.1
description: Internal operations inaccessible from the public internet.
version: 0.1.0
tags:
- name: Submissions
description: Submission operations
paths:
/submissions/{SubmissionID}/status/validator-validated:
post:
summary: (Internal endpoint) Role Validator changes status from Validating -> Validated
operationId: actionSubmissionValidated
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-uploaded:
post:
summary: (Internal endpoint) Role Validator changes status from Uploading -> Uploaded
operationId: actionSubmissionUploaded
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
- name: TargetAssetID
in: query
schema:
type: integer
format: int64
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/releaser-released:
post:
summary: (Internal endpoint) Role Releaser changes status from releasing -> released
operationId: actionSubmissionReleased
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
parameters:
SubmissionID:
name: SubmissionID
in: path
required: true
description: The unique identifier for a submission.
schema:
type: integer
format: int64
schemas:
Pagination:
type: object
required:
- Page
- Limit
properties:
Page:
type: integer
format: int32
minimum: 1
Limit:
type: integer
format: int32
minimum: 1
maximum: 100
Error:
description: Represents error object
type: object
properties:
code:
type: integer
format: int64
message:
type: string
required:
- code
- message

View File

@ -12,6 +12,8 @@ tags:
description: Script operations description: Script operations
- name: ScriptPolicy - name: ScriptPolicy
description: Script policy operations description: Script policy operations
security:
- cookieAuth: []
paths: paths:
/submissions: /submissions:
get: get:
@ -19,19 +21,19 @@ paths:
operationId: listSubmissions operationId: listSubmissions
tags: tags:
- Submissions - Submissions
parameters: requestBody:
- name: page required: true
in: query content:
required: true application/json:
schema: schema:
$ref: "#/components/schemas/Pagination" required:
- name: filter - Page
in: query type: object
required: false properties:
schema: Page:
$ref: "#/components/schemas/SubmissionFilter" $ref: "#/components/schemas/Pagination"
security: Filter:
- cookieAuth: [] $ref: "#/components/schemas/SubmissionFilter"
responses: responses:
"200": "200":
description: Successful response description: Successful response
@ -58,8 +60,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/SubmissionCreate' $ref: '#/components/schemas/SubmissionCreate'
security:
- cookieAuth: []
responses: responses:
"201": "201":
description: Successful response description: Successful response
@ -81,8 +81,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@ -116,8 +114,6 @@ paths:
schema: schema:
type: integer type: integer
format: int64 format: int64
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -135,8 +131,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -154,8 +148,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -173,8 +165,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -192,8 +182,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -211,8 +199,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -230,8 +216,6 @@ paths:
- Submissions - Submissions
parameters: parameters:
- $ref: '#/components/parameters/SubmissionID' - $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -241,10 +225,10 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-validated: /submissions/{SubmissionID}/status/trigger-upload:
post: post:
summary: (Internal endpoint) Role Validator changes status from Validating -> Validated summary: Role Admin changes status from Validated -> Uploading
operationId: actionSubmissionValidate operationId: actionSubmissionTriggerUpload
tags: tags:
- Submissions - Submissions
parameters: parameters:
@ -258,43 +242,40 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/validator-published:
post:
summary: (Internal endpoint) Role Validator changes status from Publishing -> Published
operationId: actionSubmissionPublish
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/submissions/{SubmissionID}/status/trigger-publish:
post:
summary: Role Admin changes status from Validated -> Publishing
operationId: actionSubmissionTriggerPublish
tags:
- Submissions
parameters:
- $ref: '#/components/parameters/SubmissionID'
security:
- cookieAuth: []
responses:
"204":
description: Successful response
default:
description: General Error
content:
application/json:
schema:
$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"
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
@ -306,8 +287,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptPolicyCreate' $ref: '#/components/schemas/ScriptPolicyCreate'
security:
- cookieAuth: []
responses: responses:
"201": "201":
description: Successful response description: Successful response
@ -335,8 +314,6 @@ paths:
type: string type: string
minLength: 16 minLength: 16
maxLength: 16 maxLength: 16
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@ -358,8 +335,6 @@ paths:
- ScriptPolicy - ScriptPolicy
parameters: parameters:
- $ref: '#/components/parameters/ScriptPolicyID' - $ref: '#/components/parameters/ScriptPolicyID'
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@ -386,8 +361,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptPolicyUpdate' $ref: '#/components/schemas/ScriptPolicyUpdate'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -404,8 +377,6 @@ paths:
- ScriptPolicy - ScriptPolicy
parameters: parameters:
- $ref: '#/components/parameters/ScriptPolicyID' - $ref: '#/components/parameters/ScriptPolicyID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -427,8 +398,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptCreate' $ref: '#/components/schemas/ScriptCreate'
security:
- cookieAuth: []
responses: responses:
"201": "201":
description: Successful response description: Successful response
@ -450,8 +419,6 @@ paths:
- Scripts - Scripts
parameters: parameters:
- $ref: '#/components/parameters/ScriptID' - $ref: '#/components/parameters/ScriptID'
security:
- cookieAuth: []
responses: responses:
"200": "200":
description: Successful response description: Successful response
@ -478,8 +445,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ScriptUpdate' $ref: '#/components/schemas/ScriptUpdate'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -496,8 +461,6 @@ paths:
- Scripts - Scripts
parameters: parameters:
- $ref: '#/components/parameters/ScriptID' - $ref: '#/components/parameters/ScriptID'
security:
- cookieAuth: []
responses: responses:
"204": "204":
description: Successful response description: Successful response
@ -715,6 +678,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

@ -24,12 +24,6 @@ import (
// Invoker invokes operations described by OpenAPI v3 specification. // Invoker invokes operations described by OpenAPI v3 specification.
type Invoker interface { type Invoker interface {
// ActionSubmissionPublish invokes actionSubmissionPublish operation.
//
// (Internal endpoint) Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/validator-published
ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error
// ActionSubmissionReject invokes actionSubmissionReject operation. // ActionSubmissionReject invokes actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@ -54,24 +48,18 @@ type Invoker interface {
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/submit
ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error
// ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error
// ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidate invokes actionSubmissionTriggerValidate operation.
// //
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating. // Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating.
// //
// POST /submissions/{SubmissionID}/status/trigger-validate // POST /submissions/{SubmissionID}/status/trigger-validate
ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error
// CreateScript invokes createScript operation. // CreateScript invokes createScript operation.
// //
// Create a new script. // Create a new script.
@ -126,12 +114,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.
@ -212,97 +206,6 @@ func (c *Client) requestURL(ctx context.Context) *url.URL {
return u return u
} }
// ActionSubmissionPublish invokes actionSubmissionPublish operation.
//
// (Internal endpoint) Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/validator-published
func (c *Client) ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error {
_, err := c.sendActionSubmissionPublish(ctx, params)
return err
}
func (c *Client) sendActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) (res *ActionSubmissionPublishNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionPublish"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-published"),
}
// 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, ActionSubmissionPublishOperation,
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 [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-published"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
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 := decodeActionSubmissionPublishResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionSubmissionReject invokes actionSubmissionReject operation. // ActionSubmissionReject invokes actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@ -799,21 +702,21 @@ func (c *Client) sendActionSubmissionSubmit(ctx context.Context, params ActionSu
return result, nil return result, nil
} }
// ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
func (c *Client) ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error { func (c *Client) ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error {
_, err := c.sendActionSubmissionTriggerPublish(ctx, params) _, err := c.sendActionSubmissionTriggerUpload(ctx, params)
return err return err
} }
func (c *Client) sendActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) (res *ActionSubmissionTriggerPublishNoContent, err error) { func (c *Client) sendActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) (res *ActionSubmissionTriggerUploadNoContent, err error) {
otelAttrs := []attribute.KeyValue{ otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionTriggerPublish"), otelogen.OperationID("actionSubmissionTriggerUpload"),
semconv.HTTPRequestMethodKey.String("POST"), semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/trigger-publish"), semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/trigger-upload"),
} }
// Run stopwatch. // Run stopwatch.
@ -828,7 +731,7 @@ func (c *Client) sendActionSubmissionTriggerPublish(ctx context.Context, params
c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
// Start a span for this request. // Start a span for this request.
ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionTriggerPublishOperation, ctx, span := c.cfg.Tracer.Start(ctx, ActionSubmissionTriggerUploadOperation,
trace.WithAttributes(otelAttrs...), trace.WithAttributes(otelAttrs...),
clientSpanKind, clientSpanKind,
) )
@ -865,7 +768,7 @@ func (c *Client) sendActionSubmissionTriggerPublish(ctx context.Context, params
} }
pathParts[1] = encoded pathParts[1] = encoded
} }
pathParts[2] = "/status/trigger-publish" pathParts[2] = "/status/trigger-upload"
uri.AddPathParts(u, pathParts[:]...) uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeRequest" stage = "EncodeRequest"
@ -879,7 +782,7 @@ func (c *Client) sendActionSubmissionTriggerPublish(ctx context.Context, params
var satisfied bitset var satisfied bitset
{ {
stage = "Security:CookieAuth" stage = "Security:CookieAuth"
switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerPublishOperation, r); { switch err := c.securityCookieAuth(ctx, ActionSubmissionTriggerUploadOperation, r); {
case err == nil: // if NO error case err == nil: // if NO error
satisfied[0] |= 1 << 0 satisfied[0] |= 1 << 0
case errors.Is(err, ogenerrors.ErrSkipClientSecurity): case errors.Is(err, ogenerrors.ErrSkipClientSecurity):
@ -915,7 +818,7 @@ func (c *Client) sendActionSubmissionTriggerPublish(ctx context.Context, params
defer resp.Body.Close() defer resp.Body.Close()
stage = "DecodeResponse" stage = "DecodeResponse"
result, err := decodeActionSubmissionTriggerPublishResponse(resp) result, err := decodeActionSubmissionTriggerUploadResponse(resp)
if err != nil { if err != nil {
return res, errors.Wrap(err, "decode response") return res, errors.Wrap(err, "decode response")
} }
@ -1047,97 +950,6 @@ func (c *Client) sendActionSubmissionTriggerValidate(ctx context.Context, params
return result, nil return result, nil
} }
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (c *Client) ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error {
_, err := c.sendActionSubmissionValidate(ctx, params)
return err
}
func (c *Client) sendActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) (res *ActionSubmissionValidateNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionValidate"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-validated"),
}
// 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, ActionSubmissionValidateOperation,
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 [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-validated"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
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 := decodeActionSubmissionValidateResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// CreateScript invokes createScript operation. // CreateScript invokes createScript operation.
// //
// Create a new script. // Create a new script.
@ -2200,17 +2012,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 +2170,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

@ -30,155 +30,6 @@ func (c *codeRecorder) WriteHeader(status int) {
c.ResponseWriter.WriteHeader(status) c.ResponseWriter.WriteHeader(status)
} }
// handleActionSubmissionPublishRequest handles actionSubmissionPublish operation.
//
// (Internal endpoint) Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/validator-published
func (s *Server) handleActionSubmissionPublishRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionPublish"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-published"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionPublishOperation,
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: ActionSubmissionPublishOperation,
ID: "actionSubmissionPublish",
}
)
params, err := decodeActionSubmissionPublishParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionPublishNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionPublishOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Publishing -> Published",
OperationID: "actionSubmissionPublish",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionPublishParams
Response = *ActionSubmissionPublishNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionPublishParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionPublish(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionPublish(ctx, params)
}
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 := encodeActionSubmissionPublishResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionSubmissionRejectRequest handles actionSubmissionReject operation. // handleActionSubmissionRejectRequest handles actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@ -959,22 +810,22 @@ func (s *Server) handleActionSubmissionSubmitRequest(args [1]string, argsEscaped
} }
} }
// handleActionSubmissionTriggerPublishRequest handles actionSubmissionTriggerPublish operation. // handleActionSubmissionTriggerUploadRequest handles actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { func (s *Server) handleActionSubmissionTriggerUploadRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w} statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter w = statusWriter
otelAttrs := []attribute.KeyValue{ otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionTriggerPublish"), otelogen.OperationID("actionSubmissionTriggerUpload"),
semconv.HTTPRequestMethodKey.String("POST"), semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/trigger-publish"), semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/trigger-upload"),
} }
// Start a span for this request. // Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionTriggerPublishOperation, ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionTriggerUploadOperation,
trace.WithAttributes(otelAttrs...), trace.WithAttributes(otelAttrs...),
serverSpanKind, serverSpanKind,
) )
@ -1029,15 +880,15 @@ func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, arg
} }
err error err error
opErrContext = ogenerrors.OperationContext{ opErrContext = ogenerrors.OperationContext{
Name: ActionSubmissionTriggerPublishOperation, Name: ActionSubmissionTriggerUploadOperation,
ID: "actionSubmissionTriggerPublish", ID: "actionSubmissionTriggerUpload",
} }
) )
{ {
type bitset = [1]uint8 type bitset = [1]uint8
var satisfied bitset var satisfied bitset
{ {
sctx, ok, err := s.securityCookieAuth(ctx, ActionSubmissionTriggerPublishOperation, r) sctx, ok, err := s.securityCookieAuth(ctx, ActionSubmissionTriggerUploadOperation, r)
if err != nil { if err != nil {
err = &ogenerrors.SecurityError{ err = &ogenerrors.SecurityError{
OperationContext: opErrContext, OperationContext: opErrContext,
@ -1079,7 +930,7 @@ func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, arg
return return
} }
} }
params, err := decodeActionSubmissionTriggerPublishParams(args, argsEscaped, r) params, err := decodeActionSubmissionTriggerUploadParams(args, argsEscaped, r)
if err != nil { if err != nil {
err = &ogenerrors.DecodeParamsError{ err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext, OperationContext: opErrContext,
@ -1090,13 +941,13 @@ func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, arg
return return
} }
var response *ActionSubmissionTriggerPublishNoContent var response *ActionSubmissionTriggerUploadNoContent
if m := s.cfg.Middleware; m != nil { if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{ mreq := middleware.Request{
Context: ctx, Context: ctx,
OperationName: ActionSubmissionTriggerPublishOperation, OperationName: ActionSubmissionTriggerUploadOperation,
OperationSummary: "Role Admin changes status from Validated -> Publishing", OperationSummary: "Role Admin changes status from Validated -> Uploading",
OperationID: "actionSubmissionTriggerPublish", OperationID: "actionSubmissionTriggerUpload",
Body: nil, Body: nil,
Params: middleware.Parameters{ Params: middleware.Parameters{
{ {
@ -1109,8 +960,8 @@ func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, arg
type ( type (
Request = struct{} Request = struct{}
Params = ActionSubmissionTriggerPublishParams Params = ActionSubmissionTriggerUploadParams
Response = *ActionSubmissionTriggerPublishNoContent Response = *ActionSubmissionTriggerUploadNoContent
) )
response, err = middleware.HookMiddleware[ response, err = middleware.HookMiddleware[
Request, Request,
@ -1119,14 +970,14 @@ func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, arg
]( ](
m, m,
mreq, mreq,
unpackActionSubmissionTriggerPublishParams, unpackActionSubmissionTriggerUploadParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) { func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionTriggerPublish(ctx, params) err = s.h.ActionSubmissionTriggerUpload(ctx, params)
return response, err return response, err
}, },
) )
} else { } else {
err = s.h.ActionSubmissionTriggerPublish(ctx, params) err = s.h.ActionSubmissionTriggerUpload(ctx, params)
} }
if err != nil { if err != nil {
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
@ -1145,7 +996,7 @@ func (s *Server) handleActionSubmissionTriggerPublishRequest(args [1]string, arg
return return
} }
if err := encodeActionSubmissionTriggerPublishResponse(response, w, span); err != nil { if err := encodeActionSubmissionTriggerUploadResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err) defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) { if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err) s.cfg.ErrorHandler(ctx, w, r, err)
@ -1349,155 +1200,6 @@ func (s *Server) handleActionSubmissionTriggerValidateRequest(args [1]string, ar
} }
} }
// handleActionSubmissionValidateRequest handles actionSubmissionValidate operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (s *Server) handleActionSubmissionValidateRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionValidate"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-validated"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionValidateOperation,
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: ActionSubmissionValidateOperation,
ID: "actionSubmissionValidate",
}
)
params, err := decodeActionSubmissionValidateParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionValidateNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionValidateOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Validating -> Validated",
OperationID: "actionSubmissionValidate",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionValidateParams
Response = *ActionSubmissionValidateNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionValidateParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionValidate(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionValidate(ctx, params)
}
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 := encodeActionSubmissionValidateResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleCreateScriptRequest handles createScript operation. // handleCreateScriptRequest handles createScript operation.
// //
// Create a new script. // Create a new script.
@ -3253,6 +2955,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 +3270,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 +3293,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{},
{ Raw: r,
Name: "page",
In: "query",
}: params.Page,
{
Name: "filter",
In: "query",
}: params.Filter,
},
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 +3310,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

@ -6,14 +6,12 @@ package api
type OperationName = string type OperationName = string
const ( const (
ActionSubmissionPublishOperation OperationName = "ActionSubmissionPublish"
ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject" ActionSubmissionRejectOperation OperationName = "ActionSubmissionReject"
ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges" ActionSubmissionRequestChangesOperation OperationName = "ActionSubmissionRequestChanges"
ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke" ActionSubmissionRevokeOperation OperationName = "ActionSubmissionRevoke"
ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit" ActionSubmissionSubmitOperation OperationName = "ActionSubmissionSubmit"
ActionSubmissionTriggerPublishOperation OperationName = "ActionSubmissionTriggerPublish" ActionSubmissionTriggerUploadOperation OperationName = "ActionSubmissionTriggerUpload"
ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate" ActionSubmissionTriggerValidateOperation OperationName = "ActionSubmissionTriggerValidate"
ActionSubmissionValidateOperation OperationName = "ActionSubmissionValidate"
CreateScriptOperation OperationName = "CreateScript" CreateScriptOperation OperationName = "CreateScript"
CreateScriptPolicyOperation OperationName = "CreateScriptPolicy" CreateScriptPolicyOperation OperationName = "CreateScriptPolicy"
CreateSubmissionOperation OperationName = "CreateSubmission" CreateSubmissionOperation OperationName = "CreateSubmission"
@ -23,6 +21,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

@ -15,72 +15,6 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
// ActionSubmissionPublishParams is parameters of actionSubmissionPublish operation.
type ActionSubmissionPublishParams struct {
// The unique identifier for a submission.
SubmissionID int64
}
func unpackActionSubmissionPublishParams(packed middleware.Parameters) (params ActionSubmissionPublishParams) {
{
key := middleware.ParameterKey{
Name: "SubmissionID",
In: "path",
}
params.SubmissionID = packed[key].(int64)
}
return params
}
func decodeActionSubmissionPublishParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionPublishParams, _ error) {
// Decode path: SubmissionID.
if err := func() error {
param := args[0]
if argsEscaped {
unescaped, err := url.PathUnescape(args[0])
if err != nil {
return errors.Wrap(err, "unescape path")
}
param = unescaped
}
if len(param) > 0 {
d := uri.NewPathDecoder(uri.PathDecoderConfig{
Param: "SubmissionID",
Value: param,
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
params.SubmissionID = c
return nil
}(); err != nil {
return err
}
} else {
return validate.ErrFieldRequired
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "SubmissionID",
In: "path",
Err: err,
}
}
return params, nil
}
// ActionSubmissionRejectParams is parameters of actionSubmissionReject operation. // ActionSubmissionRejectParams is parameters of actionSubmissionReject operation.
type ActionSubmissionRejectParams struct { type ActionSubmissionRejectParams struct {
// The unique identifier for a submission. // The unique identifier for a submission.
@ -345,13 +279,13 @@ func decodeActionSubmissionSubmitParams(args [1]string, argsEscaped bool, r *htt
return params, nil return params, nil
} }
// ActionSubmissionTriggerPublishParams is parameters of actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUploadParams is parameters of actionSubmissionTriggerUpload operation.
type ActionSubmissionTriggerPublishParams struct { type ActionSubmissionTriggerUploadParams struct {
// The unique identifier for a submission. // The unique identifier for a submission.
SubmissionID int64 SubmissionID int64
} }
func unpackActionSubmissionTriggerPublishParams(packed middleware.Parameters) (params ActionSubmissionTriggerPublishParams) { func unpackActionSubmissionTriggerUploadParams(packed middleware.Parameters) (params ActionSubmissionTriggerUploadParams) {
{ {
key := middleware.ParameterKey{ key := middleware.ParameterKey{
Name: "SubmissionID", Name: "SubmissionID",
@ -362,7 +296,7 @@ func unpackActionSubmissionTriggerPublishParams(packed middleware.Parameters) (p
return params return params
} }
func decodeActionSubmissionTriggerPublishParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionTriggerPublishParams, _ error) { func decodeActionSubmissionTriggerUploadParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionTriggerUploadParams, _ error) {
// Decode path: SubmissionID. // Decode path: SubmissionID.
if err := func() error { if err := func() error {
param := args[0] param := args[0]
@ -477,72 +411,6 @@ func decodeActionSubmissionTriggerValidateParams(args [1]string, argsEscaped boo
return params, nil return params, nil
} }
// ActionSubmissionValidateParams is parameters of actionSubmissionValidate operation.
type ActionSubmissionValidateParams struct {
// The unique identifier for a submission.
SubmissionID int64
}
func unpackActionSubmissionValidateParams(packed middleware.Parameters) (params ActionSubmissionValidateParams) {
{
key := middleware.ParameterKey{
Name: "SubmissionID",
In: "path",
}
params.SubmissionID = packed[key].(int64)
}
return params
}
func decodeActionSubmissionValidateParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionValidateParams, _ error) {
// Decode path: SubmissionID.
if err := func() error {
param := args[0]
if argsEscaped {
unescaped, err := url.PathUnescape(args[0])
if err != nil {
return errors.Wrap(err, "unescape path")
}
param = unescaped
}
if len(param) > 0 {
d := uri.NewPathDecoder(uri.PathDecoderConfig{
Param: "SubmissionID",
Value: param,
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
params.SubmissionID = c
return nil
}(); err != nil {
return err
}
} else {
return validate.ErrFieldRequired
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "SubmissionID",
In: "path",
Err: err,
}
}
return params, nil
}
// DeleteScriptParams is parameters of deleteScript operation. // DeleteScriptParams is parameters of deleteScript operation.
type DeleteScriptParams struct { type DeleteScriptParams struct {
// The unique identifier for a script. // The unique identifier for a script.
@ -954,117 +822,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

@ -15,57 +15,6 @@ import (
"github.com/ogen-go/ogen/validate" "github.com/ogen-go/ogen/validate"
) )
func decodeActionSubmissionPublishResponse(resp *http.Response) (res *ActionSubmissionPublishNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionPublishNoContent{}, nil
}
// 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 decodeActionSubmissionRejectResponse(resp *http.Response) (res *ActionSubmissionRejectNoContent, _ error) { func decodeActionSubmissionRejectResponse(resp *http.Response) (res *ActionSubmissionRejectNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:
@ -270,11 +219,11 @@ func decodeActionSubmissionSubmitResponse(resp *http.Response) (res *ActionSubmi
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeActionSubmissionTriggerPublishResponse(resp *http.Response) (res *ActionSubmissionTriggerPublishNoContent, _ error) { func decodeActionSubmissionTriggerUploadResponse(resp *http.Response) (res *ActionSubmissionTriggerUploadNoContent, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 204: case 204:
// Code 204. // Code 204.
return &ActionSubmissionTriggerPublishNoContent{}, nil return &ActionSubmissionTriggerUploadNoContent{}, nil
} }
// Convenient error response. // Convenient error response.
defRes, err := func() (res *ErrorStatusCode, err error) { defRes, err := func() (res *ErrorStatusCode, err error) {
@ -372,57 +321,6 @@ func decodeActionSubmissionTriggerValidateResponse(resp *http.Response) (res *Ac
return res, errors.Wrap(defRes, "error") return res, errors.Wrap(defRes, "error")
} }
func decodeActionSubmissionValidateResponse(resp *http.Response) (res *ActionSubmissionValidateNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionValidateNoContent{}, nil
}
// 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 decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) { func decodeCreateScriptResponse(resp *http.Response) (res *ID, _ error) {
switch resp.StatusCode { switch resp.StatusCode {
case 201: case 201:
@ -1142,6 +1040,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

@ -13,13 +13,6 @@ import (
ht "github.com/ogen-go/ogen/http" ht "github.com/ogen-go/ogen/http"
) )
func encodeActionSubmissionPublishResponse(response *ActionSubmissionPublishNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionRejectResponse(response *ActionSubmissionRejectNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionRejectResponse(response *ActionSubmissionRejectNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@ -48,7 +41,7 @@ func encodeActionSubmissionSubmitResponse(response *ActionSubmissionSubmitNoCont
return nil return nil
} }
func encodeActionSubmissionTriggerPublishResponse(response *ActionSubmissionTriggerPublishNoContent, w http.ResponseWriter, span trace.Span) error { func encodeActionSubmissionTriggerUploadResponse(response *ActionSubmissionTriggerUploadNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204) w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204)) span.SetStatus(codes.Ok, http.StatusText(204))
@ -62,13 +55,6 @@ func encodeActionSubmissionTriggerValidateResponse(response *ActionSubmissionTri
return nil return nil
} }
func encodeActionSubmissionValidateResponse(response *ActionSubmissionValidateNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeCreateScriptResponse(response *ID, w http.ResponseWriter, span trace.Span) error { func encodeCreateScriptResponse(response *ID, 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(201) w.WriteHeader(201)
@ -181,6 +167,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
@ -475,9 +477,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
break break
} }
switch elem[0] { switch elem[0] {
case 'p': // Prefix: "publish" case 'u': // Prefix: "upload"
origElem := elem origElem := elem
if l := len("publish"); len(elem) >= l && elem[0:l] == "publish" { if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@ -487,7 +489,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Leaf node. // Leaf node.
switch r.Method { switch r.Method {
case "POST": case "POST":
s.handleActionSubmissionTriggerPublishRequest([1]string{ s.handleActionSubmissionTriggerUploadRequest([1]string{
args[0], args[0],
}, elemIsEscaped, w, r) }, elemIsEscaped, w, r)
default: default:
@ -523,67 +525,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
elem = origElem elem = origElem
} }
elem = origElem
case 'v': // Prefix: "validator-"
origElem := elem
if l := len("validator-"); len(elem) >= l && elem[0:l] == "validator-" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'p': // Prefix: "published"
origElem := elem
if l := len("published"); len(elem) >= l && elem[0:l] == "published" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionPublishRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
elem = origElem
case 'v': // Prefix: "validated"
origElem := elem
if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionValidateRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
elem = origElem
}
elem = origElem elem = origElem
} }
@ -714,6 +655,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"
@ -1160,9 +1109,9 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
break break
} }
switch elem[0] { switch elem[0] {
case 'p': // Prefix: "publish" case 'u': // Prefix: "upload"
origElem := elem origElem := elem
if l := len("publish"); len(elem) >= l && elem[0:l] == "publish" { if l := len("upload"); len(elem) >= l && elem[0:l] == "upload" {
elem = elem[l:] elem = elem[l:]
} else { } else {
break break
@ -1172,10 +1121,10 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
// Leaf node. // Leaf node.
switch method { switch method {
case "POST": case "POST":
r.name = ActionSubmissionTriggerPublishOperation r.name = ActionSubmissionTriggerUploadOperation
r.summary = "Role Admin changes status from Validated -> Publishing" r.summary = "Role Admin changes status from Validated -> Uploading"
r.operationID = "actionSubmissionTriggerPublish" r.operationID = "actionSubmissionTriggerUpload"
r.pathPattern = "/submissions/{SubmissionID}/status/trigger-publish" r.pathPattern = "/submissions/{SubmissionID}/status/trigger-upload"
r.args = args r.args = args
r.count = 1 r.count = 1
return r, true return r, true
@ -1212,71 +1161,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
elem = origElem elem = origElem
} }
elem = origElem
case 'v': // Prefix: "validator-"
origElem := elem
if l := len("validator-"); len(elem) >= l && elem[0:l] == "validator-" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'p': // Prefix: "published"
origElem := elem
if l := len("published"); len(elem) >= l && elem[0:l] == "published" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionPublishOperation
r.summary = "(Internal endpoint) Role Validator changes status from Publishing -> Published"
r.operationID = "actionSubmissionPublish"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-published"
r.args = args
r.count = 1
return r, true
default:
return
}
}
elem = origElem
case 'v': // Prefix: "validated"
origElem := elem
if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionValidateOperation
r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
r.operationID = "actionSubmissionValidate"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated"
r.args = args
r.count = 1
return r, true
default:
return
}
}
elem = origElem
}
elem = origElem elem = origElem
} }

View File

@ -10,9 +10,6 @@ func (s *ErrorStatusCode) Error() string {
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response) return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
} }
// ActionSubmissionPublishNoContent is response for ActionSubmissionPublish operation.
type ActionSubmissionPublishNoContent struct{}
// ActionSubmissionRejectNoContent is response for ActionSubmissionReject operation. // ActionSubmissionRejectNoContent is response for ActionSubmissionReject operation.
type ActionSubmissionRejectNoContent struct{} type ActionSubmissionRejectNoContent struct{}
@ -25,15 +22,12 @@ type ActionSubmissionRevokeNoContent struct{}
// ActionSubmissionSubmitNoContent is response for ActionSubmissionSubmit operation. // ActionSubmissionSubmitNoContent is response for ActionSubmissionSubmit operation.
type ActionSubmissionSubmitNoContent struct{} type ActionSubmissionSubmitNoContent struct{}
// ActionSubmissionTriggerPublishNoContent is response for ActionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUploadNoContent is response for ActionSubmissionTriggerUpload operation.
type ActionSubmissionTriggerPublishNoContent struct{} type ActionSubmissionTriggerUploadNoContent struct{}
// ActionSubmissionTriggerValidateNoContent is response for ActionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidateNoContent is response for ActionSubmissionTriggerValidate operation.
type ActionSubmissionTriggerValidateNoContent struct{} type ActionSubmissionTriggerValidateNoContent struct{}
// ActionSubmissionValidateNoContent is response for ActionSubmissionValidate operation.
type ActionSubmissionValidateNoContent struct{}
type CookieAuth struct { type CookieAuth struct {
APIKey string APIKey string
} }
@ -122,6 +116,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 +258,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 +581,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

@ -8,12 +8,6 @@ import (
// Handler handles operations described by OpenAPI v3 specification. // Handler handles operations described by OpenAPI v3 specification.
type Handler interface { type Handler interface {
// ActionSubmissionPublish implements actionSubmissionPublish operation.
//
// (Internal endpoint) Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/validator-published
ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error
// ActionSubmissionReject implements actionSubmissionReject operation. // ActionSubmissionReject implements actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@ -38,24 +32,18 @@ type Handler interface {
// //
// POST /submissions/{SubmissionID}/status/submit // POST /submissions/{SubmissionID}/status/submit
ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error ActionSubmissionSubmit(ctx context.Context, params ActionSubmissionSubmitParams) error
// ActionSubmissionTriggerPublish implements actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error
// ActionSubmissionTriggerValidate implements actionSubmissionTriggerValidate operation. // ActionSubmissionTriggerValidate implements actionSubmissionTriggerValidate operation.
// //
// Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating. // Role Reviewer triggers validation and changes status from Submitted|Accepted -> Validating.
// //
// POST /submissions/{SubmissionID}/status/trigger-validate // POST /submissions/{SubmissionID}/status/trigger-validate
ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error ActionSubmissionTriggerValidate(ctx context.Context, params ActionSubmissionTriggerValidateParams) error
// ActionSubmissionValidate implements actionSubmissionValidate operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error
// CreateScript implements createScript operation. // CreateScript implements createScript operation.
// //
// Create a new script. // Create a new script.
@ -110,12 +98,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

@ -13,15 +13,6 @@ type UnimplementedHandler struct{}
var _ Handler = UnimplementedHandler{} var _ Handler = UnimplementedHandler{}
// ActionSubmissionPublish implements actionSubmissionPublish operation.
//
// (Internal endpoint) Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/validator-published
func (UnimplementedHandler) ActionSubmissionPublish(ctx context.Context, params ActionSubmissionPublishParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionReject implements actionSubmissionReject operation. // ActionSubmissionReject implements actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@ -58,12 +49,12 @@ func (UnimplementedHandler) ActionSubmissionSubmit(ctx context.Context, params A
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionTriggerPublish implements actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload implements actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
func (UnimplementedHandler) ActionSubmissionTriggerPublish(ctx context.Context, params ActionSubmissionTriggerPublishParams) error { func (UnimplementedHandler) ActionSubmissionTriggerUpload(ctx context.Context, params ActionSubmissionTriggerUploadParams) error {
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
@ -76,15 +67,6 @@ func (UnimplementedHandler) ActionSubmissionTriggerValidate(ctx context.Context,
return ht.ErrNotImplemented return ht.ErrNotImplemented
} }
// ActionSubmissionValidate implements actionSubmissionValidate operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (UnimplementedHandler) ActionSubmissionValidate(ctx context.Context, params ActionSubmissionValidateParams) error {
return ht.ErrNotImplemented
}
// CreateScript implements createScript operation. // CreateScript implements createScript operation.
// //
// Create a new script. // Create a new script.
@ -166,12 +148,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

@ -2,16 +2,19 @@ package cmds
import ( import (
"fmt" "fmt"
"net/http"
"git.itzana.me/strafesnet/go-grpc/auth" "git.itzana.me/strafesnet/go-grpc/auth"
"git.itzana.me/strafesnet/maps-service/pkg/api" "git.itzana.me/strafesnet/maps-service/pkg/api"
"git.itzana.me/strafesnet/maps-service/pkg/datastore/gormstore" "git.itzana.me/strafesnet/maps-service/pkg/datastore/gormstore"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/service" "git.itzana.me/strafesnet/maps-service/pkg/service"
"git.itzana.me/strafesnet/maps-service/pkg/service_internal"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"net/http"
) )
func NewServeCommand() *cli.Command { func NewServeCommand() *cli.Command {
@ -62,6 +65,12 @@ func NewServeCommand() *cli.Command {
Value: 8080, Value: 8080,
EnvVars: []string{"PORT"}, EnvVars: []string{"PORT"},
}, },
&cli.IntFlag{
Name: "port-internal",
Usage: "Port to listen on for internal api",
Value: 8081,
EnvVars: []string{"PORT_INTERNAL"},
},
&cli.StringFlag{ &cli.StringFlag{
Name: "auth-rpc-host", Name: "auth-rpc-host",
Usage: "Host of auth rpc", Usage: "Host of auth rpc",
@ -119,5 +128,17 @@ func serve(ctx *cli.Context) error {
log.WithError(err).Fatal("failed to initialize api server") log.WithError(err).Fatal("failed to initialize api server")
} }
svc2 := &service_internal.Service{
DB: db,
Nats: js,
}
srv2, err := internal.NewServer(svc2, nil, internal.WithPathPrefix("/v1"))
if err != nil {
log.WithError(err).Fatal("failed to initialize api server")
}
// idk how else to do this
go http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port-internal")), srv2)
return http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv) return http.ListenAndServe(fmt.Sprintf(":%d", ctx.Int("port")), srv)
} }

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
}

283
pkg/internal/oas_cfg_gen.go Normal file
View File

@ -0,0 +1,283 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/middleware"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/otelogen"
)
var (
// Allocate option closure once.
clientSpanKind = trace.WithSpanKind(trace.SpanKindClient)
// Allocate option closure once.
serverSpanKind = trace.WithSpanKind(trace.SpanKindServer)
)
type (
optionFunc[C any] func(*C)
otelOptionFunc func(*otelConfig)
)
type otelConfig struct {
TracerProvider trace.TracerProvider
Tracer trace.Tracer
MeterProvider metric.MeterProvider
Meter metric.Meter
}
func (cfg *otelConfig) initOTEL() {
if cfg.TracerProvider == nil {
cfg.TracerProvider = otel.GetTracerProvider()
}
if cfg.MeterProvider == nil {
cfg.MeterProvider = otel.GetMeterProvider()
}
cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name,
trace.WithInstrumentationVersion(otelogen.SemVersion()),
)
cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name,
metric.WithInstrumentationVersion(otelogen.SemVersion()),
)
}
// ErrorHandler is error handler.
type ErrorHandler = ogenerrors.ErrorHandler
type serverConfig struct {
otelConfig
NotFound http.HandlerFunc
MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)
ErrorHandler ErrorHandler
Prefix string
Middleware Middleware
MaxMultipartMemory int64
}
// ServerOption is server config option.
type ServerOption interface {
applyServer(*serverConfig)
}
var _ ServerOption = (optionFunc[serverConfig])(nil)
func (o optionFunc[C]) applyServer(c *C) {
o(c)
}
var _ ServerOption = (otelOptionFunc)(nil)
func (o otelOptionFunc) applyServer(c *serverConfig) {
o(&c.otelConfig)
}
func newServerConfig(opts ...ServerOption) serverConfig {
cfg := serverConfig{
NotFound: http.NotFound,
MethodNotAllowed: func(w http.ResponseWriter, r *http.Request, allowed string) {
status := http.StatusMethodNotAllowed
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Methods", allowed)
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
status = http.StatusNoContent
} else {
w.Header().Set("Allow", allowed)
}
w.WriteHeader(status)
},
ErrorHandler: ogenerrors.DefaultErrorHandler,
Middleware: nil,
MaxMultipartMemory: 32 << 20, // 32 MB
}
for _, opt := range opts {
opt.applyServer(&cfg)
}
cfg.initOTEL()
return cfg
}
type baseServer struct {
cfg serverConfig
requests metric.Int64Counter
errors metric.Int64Counter
duration metric.Float64Histogram
}
func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) {
s.cfg.NotFound(w, r)
}
func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, allowed string) {
s.cfg.MethodNotAllowed(w, r, allowed)
}
func (cfg serverConfig) baseServer() (s baseServer, err error) {
s = baseServer{cfg: cfg}
if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil {
return s, err
}
if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil {
return s, err
}
if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil {
return s, err
}
return s, nil
}
type clientConfig struct {
otelConfig
Client ht.Client
}
// ClientOption is client config option.
type ClientOption interface {
applyClient(*clientConfig)
}
var _ ClientOption = (optionFunc[clientConfig])(nil)
func (o optionFunc[C]) applyClient(c *C) {
o(c)
}
var _ ClientOption = (otelOptionFunc)(nil)
func (o otelOptionFunc) applyClient(c *clientConfig) {
o(&c.otelConfig)
}
func newClientConfig(opts ...ClientOption) clientConfig {
cfg := clientConfig{
Client: http.DefaultClient,
}
for _, opt := range opts {
opt.applyClient(&cfg)
}
cfg.initOTEL()
return cfg
}
type baseClient struct {
cfg clientConfig
requests metric.Int64Counter
errors metric.Int64Counter
duration metric.Float64Histogram
}
func (cfg clientConfig) baseClient() (c baseClient, err error) {
c = baseClient{cfg: cfg}
if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil {
return c, err
}
if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil {
return c, err
}
if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil {
return c, err
}
return c, nil
}
// Option is config option.
type Option interface {
ServerOption
ClientOption
}
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
//
// If none is specified, the global provider is used.
func WithTracerProvider(provider trace.TracerProvider) Option {
return otelOptionFunc(func(cfg *otelConfig) {
if provider != nil {
cfg.TracerProvider = provider
}
})
}
// WithMeterProvider specifies a meter provider to use for creating a meter.
//
// If none is specified, the otel.GetMeterProvider() is used.
func WithMeterProvider(provider metric.MeterProvider) Option {
return otelOptionFunc(func(cfg *otelConfig) {
if provider != nil {
cfg.MeterProvider = provider
}
})
}
// WithClient specifies http client to use.
func WithClient(client ht.Client) ClientOption {
return optionFunc[clientConfig](func(cfg *clientConfig) {
if client != nil {
cfg.Client = client
}
})
}
// WithNotFound specifies Not Found handler to use.
func WithNotFound(notFound http.HandlerFunc) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if notFound != nil {
cfg.NotFound = notFound
}
})
}
// WithMethodNotAllowed specifies Method Not Allowed handler to use.
func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if methodNotAllowed != nil {
cfg.MethodNotAllowed = methodNotAllowed
}
})
}
// WithErrorHandler specifies error handler to use.
func WithErrorHandler(h ErrorHandler) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if h != nil {
cfg.ErrorHandler = h
}
})
}
// WithPathPrefix specifies server path prefix.
func WithPathPrefix(prefix string) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
cfg.Prefix = prefix
})
}
// WithMiddleware specifies middlewares to use.
func WithMiddleware(m ...Middleware) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
switch len(m) {
case 0:
cfg.Middleware = nil
case 1:
cfg.Middleware = m[0]
default:
cfg.Middleware = middleware.ChainMiddlewares(m...)
}
})
}
// WithMaxMultipartMemory specifies limit of memory for storing file parts.
// File parts which can't be stored in memory will be stored on disk in temporary files.
func WithMaxMultipartMemory(max int64) ServerOption {
return optionFunc[serverConfig](func(cfg *serverConfig) {
if max > 0 {
cfg.MaxMultipartMemory = max
}
})
}

View File

@ -0,0 +1,390 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
"net/url"
"strings"
"time"
"github.com/go-faster/errors"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
"github.com/ogen-go/ogen/conv"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/otelogen"
"github.com/ogen-go/ogen/uri"
)
// Invoker invokes operations described by OpenAPI v3 specification.
type Invoker interface {
// ActionSubmissionReleased invokes actionSubmissionReleased operation.
//
// (Internal endpoint) Role Releaser changes status from releasing -> released.
//
// POST /submissions/{SubmissionID}/status/releaser-released
ActionSubmissionReleased(ctx context.Context, params ActionSubmissionReleasedParams) error
// ActionSubmissionUploaded invokes actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
ActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) error
// ActionSubmissionValidated invokes actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
}
// Client implements OAS client.
type Client struct {
serverURL *url.URL
baseClient
}
type errorHandler interface {
NewError(ctx context.Context, err error) *ErrorStatusCode
}
var _ Handler = struct {
errorHandler
*Client
}{}
func trimTrailingSlashes(u *url.URL) {
u.Path = strings.TrimRight(u.Path, "/")
u.RawPath = strings.TrimRight(u.RawPath, "/")
}
// NewClient initializes new Client defined by OAS.
func NewClient(serverURL string, opts ...ClientOption) (*Client, error) {
u, err := url.Parse(serverURL)
if err != nil {
return nil, err
}
trimTrailingSlashes(u)
c, err := newClientConfig(opts...).baseClient()
if err != nil {
return nil, err
}
return &Client{
serverURL: u,
baseClient: c,
}, nil
}
type serverURLKey struct{}
// WithServerURL sets context key to override server URL.
func WithServerURL(ctx context.Context, u *url.URL) context.Context {
return context.WithValue(ctx, serverURLKey{}, u)
}
func (c *Client) requestURL(ctx context.Context) *url.URL {
u, ok := ctx.Value(serverURLKey{}).(*url.URL)
if !ok {
return c.serverURL
}
return u
}
// ActionSubmissionReleased invokes actionSubmissionReleased operation.
//
// (Internal endpoint) Role Releaser changes status from releasing -> released.
//
// POST /submissions/{SubmissionID}/status/releaser-released
func (c *Client) ActionSubmissionReleased(ctx context.Context, params ActionSubmissionReleasedParams) error {
_, err := c.sendActionSubmissionReleased(ctx, params)
return err
}
func (c *Client) sendActionSubmissionReleased(ctx context.Context, params ActionSubmissionReleasedParams) (res *ActionSubmissionReleasedNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionReleased"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/releaser-released"),
}
// 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, ActionSubmissionReleasedOperation,
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 [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/releaser-released"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
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 := decodeActionSubmissionReleasedResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionSubmissionUploaded invokes actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (c *Client) ActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) error {
_, err := c.sendActionSubmissionUploaded(ctx, params)
return err
}
func (c *Client) sendActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) (res *ActionSubmissionUploadedNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionUploaded"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-uploaded"),
}
// 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, ActionSubmissionUploadedOperation,
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 [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-uploaded"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeQueryParams"
q := uri.NewQueryEncoder()
{
// Encode "TargetAssetID" parameter.
cfg := uri.QueryParameterEncodingConfig{
Name: "TargetAssetID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
if val, ok := params.TargetAssetID.Get(); ok {
return e.EncodeValue(conv.Int64ToString(val))
}
return nil
}); err != nil {
return res, errors.Wrap(err, "encode query")
}
}
u.RawQuery = q.Values().Encode()
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
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 := decodeActionSubmissionUploadedResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}
// ActionSubmissionValidated invokes actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (c *Client) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
_, err := c.sendActionSubmissionValidated(ctx, params)
return err
}
func (c *Client) sendActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) (res *ActionSubmissionValidatedNoContent, err error) {
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionValidated"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-validated"),
}
// 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, ActionSubmissionValidatedOperation,
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 [3]string
pathParts[0] = "/submissions/"
{
// Encode "SubmissionID" parameter.
e := uri.NewPathEncoder(uri.PathEncoderConfig{
Param: "SubmissionID",
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
return e.EncodeValue(conv.Int64ToString(params.SubmissionID))
}(); err != nil {
return res, errors.Wrap(err, "encode path")
}
encoded, err := e.Result()
if err != nil {
return res, errors.Wrap(err, "encode path")
}
pathParts[1] = encoded
}
pathParts[2] = "/status/validator-validated"
uri.AddPathParts(u, pathParts[:]...)
stage = "EncodeRequest"
r, err := ht.NewRequest(ctx, "POST", u)
if err != nil {
return res, errors.Wrap(err, "create request")
}
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 := decodeActionSubmissionValidatedResponse(resp)
if err != nil {
return res, errors.Wrap(err, "decode response")
}
return result, nil
}

View File

@ -0,0 +1,482 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
"net/http"
"time"
"github.com/go-faster/errors"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
"github.com/ogen-go/ogen/middleware"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/otelogen"
)
type codeRecorder struct {
http.ResponseWriter
status int
}
func (c *codeRecorder) WriteHeader(status int) {
c.status = status
c.ResponseWriter.WriteHeader(status)
}
// handleActionSubmissionReleasedRequest handles actionSubmissionReleased operation.
//
// (Internal endpoint) Role Releaser changes status from releasing -> released.
//
// POST /submissions/{SubmissionID}/status/releaser-released
func (s *Server) handleActionSubmissionReleasedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionReleased"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/releaser-released"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionReleasedOperation,
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: ActionSubmissionReleasedOperation,
ID: "actionSubmissionReleased",
}
)
params, err := decodeActionSubmissionReleasedParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionReleasedNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionReleasedOperation,
OperationSummary: "(Internal endpoint) Role Releaser changes status from releasing -> released",
OperationID: "actionSubmissionReleased",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionReleasedParams
Response = *ActionSubmissionReleasedNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionReleasedParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionReleased(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionReleased(ctx, params)
}
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 := encodeActionSubmissionReleasedResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionSubmissionUploadedRequest handles actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (s *Server) handleActionSubmissionUploadedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionUploaded"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-uploaded"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionUploadedOperation,
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: ActionSubmissionUploadedOperation,
ID: "actionSubmissionUploaded",
}
)
params, err := decodeActionSubmissionUploadedParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionUploadedNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionUploadedOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded",
OperationID: "actionSubmissionUploaded",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
{
Name: "TargetAssetID",
In: "query",
}: params.TargetAssetID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionUploadedParams
Response = *ActionSubmissionUploadedNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionUploadedParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionUploaded(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionUploaded(ctx, params)
}
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 := encodeActionSubmissionUploadedResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}
// handleActionSubmissionValidatedRequest handles actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (s *Server) handleActionSubmissionValidatedRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
statusWriter := &codeRecorder{ResponseWriter: w}
w = statusWriter
otelAttrs := []attribute.KeyValue{
otelogen.OperationID("actionSubmissionValidated"),
semconv.HTTPRequestMethodKey.String("POST"),
semconv.HTTPRouteKey.String("/submissions/{SubmissionID}/status/validator-validated"),
}
// Start a span for this request.
ctx, span := s.cfg.Tracer.Start(r.Context(), ActionSubmissionValidatedOperation,
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: ActionSubmissionValidatedOperation,
ID: "actionSubmissionValidated",
}
)
params, err := decodeActionSubmissionValidatedParams(args, argsEscaped, r)
if err != nil {
err = &ogenerrors.DecodeParamsError{
OperationContext: opErrContext,
Err: err,
}
defer recordError("DecodeParams", err)
s.cfg.ErrorHandler(ctx, w, r, err)
return
}
var response *ActionSubmissionValidatedNoContent
if m := s.cfg.Middleware; m != nil {
mreq := middleware.Request{
Context: ctx,
OperationName: ActionSubmissionValidatedOperation,
OperationSummary: "(Internal endpoint) Role Validator changes status from Validating -> Validated",
OperationID: "actionSubmissionValidated",
Body: nil,
Params: middleware.Parameters{
{
Name: "SubmissionID",
In: "path",
}: params.SubmissionID,
},
Raw: r,
}
type (
Request = struct{}
Params = ActionSubmissionValidatedParams
Response = *ActionSubmissionValidatedNoContent
)
response, err = middleware.HookMiddleware[
Request,
Params,
Response,
](
m,
mreq,
unpackActionSubmissionValidatedParams,
func(ctx context.Context, request Request, params Params) (response Response, err error) {
err = s.h.ActionSubmissionValidated(ctx, params)
return response, err
},
)
} else {
err = s.h.ActionSubmissionValidated(ctx, params)
}
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 := encodeActionSubmissionValidatedResponse(response, w, span); err != nil {
defer recordError("EncodeResponse", err)
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
s.cfg.ErrorHandler(ctx, w, r, err)
}
return
}
}

View File

@ -0,0 +1,126 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"math/bits"
"strconv"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/validate"
)
// Encode implements json.Marshaler.
func (s *Error) Encode(e *jx.Encoder) {
e.ObjStart()
s.encodeFields(e)
e.ObjEnd()
}
// encodeFields encodes fields.
func (s *Error) encodeFields(e *jx.Encoder) {
{
e.FieldStart("code")
e.Int64(s.Code)
}
{
e.FieldStart("message")
e.Str(s.Message)
}
}
var jsonFieldsNameOfError = [2]string{
0: "code",
1: "message",
}
// Decode decodes Error from json.
func (s *Error) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Error to nil")
}
var requiredBitSet [1]uint8
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) {
case "code":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int64()
s.Code = int64(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"code\"")
}
case "message":
requiredBitSet[0] |= 1 << 1
if err := func() error {
v, err := d.Str()
s.Message = string(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"message\"")
}
default:
return d.Skip()
}
return nil
}); err != nil {
return errors.Wrap(err, "decode Error")
}
// 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(jsonFieldsNameOfError) {
name = jsonFieldsNameOfError[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 *Error) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *Error) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}

View File

@ -0,0 +1,42 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
"go.opentelemetry.io/otel/attribute"
)
// Labeler is used to allow adding custom attributes to the server request metrics.
type Labeler struct {
attrs []attribute.KeyValue
}
// Add attributes to the Labeler.
func (l *Labeler) Add(attrs ...attribute.KeyValue) {
l.attrs = append(l.attrs, attrs...)
}
// AttributeSet returns the attributes added to the Labeler as an attribute.Set.
func (l *Labeler) AttributeSet() attribute.Set {
return attribute.NewSet(l.attrs...)
}
type labelerContextKey struct{}
// LabelerFromContext retrieves the Labeler from the provided context, if present.
//
// If no Labeler was found in the provided context a new, empty Labeler is returned and the second
// return value is false. In this case it is safe to use the Labeler but any attributes added to
// it will not be used.
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok {
return l, true
}
return &Labeler{}, false
}
func contextWithLabeler(ctx context.Context, l *Labeler) context.Context {
return context.WithValue(ctx, labelerContextKey{}, l)
}

View File

@ -0,0 +1,10 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"github.com/ogen-go/ogen/middleware"
)
// Middleware is middleware type.
type Middleware = middleware.Middleware

View File

@ -0,0 +1,12 @@
// Code generated by ogen, DO NOT EDIT.
package api
// OperationName is the ogen operation name
type OperationName = string
const (
ActionSubmissionReleasedOperation OperationName = "ActionSubmissionReleased"
ActionSubmissionUploadedOperation OperationName = "ActionSubmissionUploaded"
ActionSubmissionValidatedOperation OperationName = "ActionSubmissionValidated"
)

View File

@ -0,0 +1,266 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"net/url"
"github.com/go-faster/errors"
"github.com/ogen-go/ogen/conv"
"github.com/ogen-go/ogen/middleware"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/uri"
"github.com/ogen-go/ogen/validate"
)
// ActionSubmissionReleasedParams is parameters of actionSubmissionReleased operation.
type ActionSubmissionReleasedParams struct {
// The unique identifier for a submission.
SubmissionID int64
}
func unpackActionSubmissionReleasedParams(packed middleware.Parameters) (params ActionSubmissionReleasedParams) {
{
key := middleware.ParameterKey{
Name: "SubmissionID",
In: "path",
}
params.SubmissionID = packed[key].(int64)
}
return params
}
func decodeActionSubmissionReleasedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionReleasedParams, _ error) {
// Decode path: SubmissionID.
if err := func() error {
param := args[0]
if argsEscaped {
unescaped, err := url.PathUnescape(args[0])
if err != nil {
return errors.Wrap(err, "unescape path")
}
param = unescaped
}
if len(param) > 0 {
d := uri.NewPathDecoder(uri.PathDecoderConfig{
Param: "SubmissionID",
Value: param,
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
params.SubmissionID = c
return nil
}(); err != nil {
return err
}
} else {
return validate.ErrFieldRequired
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "SubmissionID",
In: "path",
Err: err,
}
}
return params, nil
}
// ActionSubmissionUploadedParams is parameters of actionSubmissionUploaded operation.
type ActionSubmissionUploadedParams struct {
// The unique identifier for a submission.
SubmissionID int64
TargetAssetID OptInt64
}
func unpackActionSubmissionUploadedParams(packed middleware.Parameters) (params ActionSubmissionUploadedParams) {
{
key := middleware.ParameterKey{
Name: "SubmissionID",
In: "path",
}
params.SubmissionID = packed[key].(int64)
}
{
key := middleware.ParameterKey{
Name: "TargetAssetID",
In: "query",
}
if v, ok := packed[key]; ok {
params.TargetAssetID = v.(OptInt64)
}
}
return params
}
func decodeActionSubmissionUploadedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionUploadedParams, _ error) {
q := uri.NewQueryDecoder(r.URL.Query())
// Decode path: SubmissionID.
if err := func() error {
param := args[0]
if argsEscaped {
unescaped, err := url.PathUnescape(args[0])
if err != nil {
return errors.Wrap(err, "unescape path")
}
param = unescaped
}
if len(param) > 0 {
d := uri.NewPathDecoder(uri.PathDecoderConfig{
Param: "SubmissionID",
Value: param,
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
params.SubmissionID = c
return nil
}(); err != nil {
return err
}
} else {
return validate.ErrFieldRequired
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "SubmissionID",
In: "path",
Err: err,
}
}
// Decode query: TargetAssetID.
if err := func() error {
cfg := uri.QueryParameterDecodingConfig{
Name: "TargetAssetID",
Style: uri.QueryStyleForm,
Explode: true,
}
if err := q.HasParam(cfg); err == nil {
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
var paramsDotTargetAssetIDVal int64
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
paramsDotTargetAssetIDVal = c
return nil
}(); err != nil {
return err
}
params.TargetAssetID.SetTo(paramsDotTargetAssetIDVal)
return nil
}); err != nil {
return err
}
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "TargetAssetID",
In: "query",
Err: err,
}
}
return params, nil
}
// ActionSubmissionValidatedParams is parameters of actionSubmissionValidated operation.
type ActionSubmissionValidatedParams struct {
// The unique identifier for a submission.
SubmissionID int64
}
func unpackActionSubmissionValidatedParams(packed middleware.Parameters) (params ActionSubmissionValidatedParams) {
{
key := middleware.ParameterKey{
Name: "SubmissionID",
In: "path",
}
params.SubmissionID = packed[key].(int64)
}
return params
}
func decodeActionSubmissionValidatedParams(args [1]string, argsEscaped bool, r *http.Request) (params ActionSubmissionValidatedParams, _ error) {
// Decode path: SubmissionID.
if err := func() error {
param := args[0]
if argsEscaped {
unescaped, err := url.PathUnescape(args[0])
if err != nil {
return errors.Wrap(err, "unescape path")
}
param = unescaped
}
if len(param) > 0 {
d := uri.NewPathDecoder(uri.PathDecoderConfig{
Param: "SubmissionID",
Value: param,
Style: uri.PathStyleSimple,
Explode: false,
})
if err := func() error {
val, err := d.DecodeValue()
if err != nil {
return err
}
c, err := conv.ToInt64(val)
if err != nil {
return err
}
params.SubmissionID = c
return nil
}(); err != nil {
return err
}
} else {
return validate.ErrFieldRequired
}
return nil
}(); err != nil {
return params, &ogenerrors.DecodeParamError{
Name: "SubmissionID",
In: "path",
Err: err,
}
}
return params, nil
}

View File

@ -0,0 +1,3 @@
// Code generated by ogen, DO NOT EDIT.
package api

View File

@ -0,0 +1,3 @@
// Code generated by ogen, DO NOT EDIT.
package api

View File

@ -0,0 +1,168 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"io"
"mime"
"net/http"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"github.com/ogen-go/ogen/ogenerrors"
"github.com/ogen-go/ogen/validate"
)
func decodeActionSubmissionReleasedResponse(resp *http.Response) (res *ActionSubmissionReleasedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionReleasedNoContent{}, nil
}
// 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 decodeActionSubmissionUploadedResponse(resp *http.Response) (res *ActionSubmissionUploadedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionUploadedNoContent{}, nil
}
// 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 decodeActionSubmissionValidatedResponse(resp *http.Response) (res *ActionSubmissionValidatedNoContent, _ error) {
switch resp.StatusCode {
case 204:
// Code 204.
return &ActionSubmissionValidatedNoContent{}, nil
}
// 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")
}

View File

@ -0,0 +1,62 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"github.com/go-faster/errors"
"github.com/go-faster/jx"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
ht "github.com/ogen-go/ogen/http"
)
func encodeActionSubmissionReleasedResponse(response *ActionSubmissionReleasedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionUploadedResponse(response *ActionSubmissionUploadedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeActionSubmissionValidatedResponse(response *ActionSubmissionValidatedNoContent, w http.ResponseWriter, span trace.Span) error {
w.WriteHeader(204)
span.SetStatus(codes.Ok, http.StatusText(204))
return nil
}
func encodeErrorResponse(response *ErrorStatusCode, w http.ResponseWriter, span trace.Span) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
code := response.StatusCode
if code == 0 {
// Set default status code.
code = http.StatusOK
}
w.WriteHeader(code)
if st := http.StatusText(code); code >= http.StatusBadRequest {
span.SetStatus(codes.Error, st)
} else {
span.SetStatus(codes.Ok, st)
}
e := new(jx.Encoder)
response.Response.Encode(e)
if _, err := e.WriteTo(w); err != nil {
return errors.Wrap(err, "write")
}
if code >= http.StatusInternalServerError {
return errors.Wrapf(ht.ErrInternalServerErrorResponse, "code: %d, message: %s", code, http.StatusText(code))
}
return nil
}

View File

@ -0,0 +1,387 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"net/http"
"net/url"
"strings"
"github.com/ogen-go/ogen/uri"
)
func (s *Server) cutPrefix(path string) (string, bool) {
prefix := s.cfg.Prefix
if prefix == "" {
return path, true
}
if !strings.HasPrefix(path, prefix) {
// Prefix doesn't match.
return "", false
}
// Cut prefix from the path.
return strings.TrimPrefix(path, prefix), true
}
// ServeHTTP serves http request as defined by OpenAPI v3 specification,
// calling handler that matches the path or returning not found error.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
elem := r.URL.Path
elemIsEscaped := false
if rawPath := r.URL.RawPath; rawPath != "" {
if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok {
elem = normalized
elemIsEscaped = strings.ContainsRune(elem, '%')
}
}
elem, ok := s.cutPrefix(elem)
if !ok || len(elem) == 0 {
s.notFound(w, r)
return
}
args := [1]string{}
// Static code generated router with unwrapped path search.
switch {
default:
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/submissions/"
origElem := elem
if l := len("/submissions/"); len(elem) >= l && elem[0:l] == "/submissions/" {
elem = elem[l:]
} else {
break
}
// Param: "SubmissionID"
// Match until "/"
idx := strings.IndexByte(elem, '/')
if idx < 0 {
idx = len(elem)
}
args[0] = elem[:idx]
elem = elem[idx:]
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/status/"
origElem := elem
if l := len("/status/"); len(elem) >= l && elem[0:l] == "/status/" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'r': // Prefix: "releaser-released"
origElem := elem
if l := len("releaser-released"); len(elem) >= l && elem[0:l] == "releaser-released" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionReleasedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
elem = origElem
case 'v': // Prefix: "validator-"
origElem := elem
if l := len("validator-"); len(elem) >= l && elem[0:l] == "validator-" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'u': // Prefix: "uploaded"
origElem := elem
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionUploadedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
elem = origElem
case 'v': // Prefix: "validated"
origElem := elem
if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch r.Method {
case "POST":
s.handleActionSubmissionValidatedRequest([1]string{
args[0],
}, elemIsEscaped, w, r)
default:
s.notAllowed(w, r, "POST")
}
return
}
elem = origElem
}
elem = origElem
}
elem = origElem
}
elem = origElem
}
}
s.notFound(w, r)
}
// Route is route object.
type Route struct {
name string
summary string
operationID string
pathPattern string
count int
args [1]string
}
// Name returns ogen operation name.
//
// It is guaranteed to be unique and not empty.
func (r Route) Name() string {
return r.name
}
// Summary returns OpenAPI summary.
func (r Route) Summary() string {
return r.summary
}
// OperationID returns OpenAPI operationId.
func (r Route) OperationID() string {
return r.operationID
}
// PathPattern returns OpenAPI path.
func (r Route) PathPattern() string {
return r.pathPattern
}
// Args returns parsed arguments.
func (r Route) Args() []string {
return r.args[:r.count]
}
// FindRoute finds Route for given method and path.
//
// Note: this method does not unescape path or handle reserved characters in path properly. Use FindPath instead.
func (s *Server) FindRoute(method, path string) (Route, bool) {
return s.FindPath(method, &url.URL{Path: path})
}
// FindPath finds Route for given method and URL.
func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
var (
elem = u.Path
args = r.args
)
if rawPath := u.RawPath; rawPath != "" {
if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok {
elem = normalized
}
defer func() {
for i, arg := range r.args[:r.count] {
if unescaped, err := url.PathUnescape(arg); err == nil {
r.args[i] = unescaped
}
}
}()
}
elem, ok := s.cutPrefix(elem)
if !ok {
return r, false
}
// Static code generated router with unwrapped path search.
switch {
default:
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/submissions/"
origElem := elem
if l := len("/submissions/"); len(elem) >= l && elem[0:l] == "/submissions/" {
elem = elem[l:]
} else {
break
}
// Param: "SubmissionID"
// Match until "/"
idx := strings.IndexByte(elem, '/')
if idx < 0 {
idx = len(elem)
}
args[0] = elem[:idx]
elem = elem[idx:]
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/status/"
origElem := elem
if l := len("/status/"); len(elem) >= l && elem[0:l] == "/status/" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'r': // Prefix: "releaser-released"
origElem := elem
if l := len("releaser-released"); len(elem) >= l && elem[0:l] == "releaser-released" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionReleasedOperation
r.summary = "(Internal endpoint) Role Releaser changes status from releasing -> released"
r.operationID = "actionSubmissionReleased"
r.pathPattern = "/submissions/{SubmissionID}/status/releaser-released"
r.args = args
r.count = 1
return r, true
default:
return
}
}
elem = origElem
case 'v': // Prefix: "validator-"
origElem := elem
if l := len("validator-"); len(elem) >= l && elem[0:l] == "validator-" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
break
}
switch elem[0] {
case 'u': // Prefix: "uploaded"
origElem := elem
if l := len("uploaded"); len(elem) >= l && elem[0:l] == "uploaded" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionUploadedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Uploading -> Uploaded"
r.operationID = "actionSubmissionUploaded"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-uploaded"
r.args = args
r.count = 1
return r, true
default:
return
}
}
elem = origElem
case 'v': // Prefix: "validated"
origElem := elem
if l := len("validated"); len(elem) >= l && elem[0:l] == "validated" {
elem = elem[l:]
} else {
break
}
if len(elem) == 0 {
// Leaf node.
switch method {
case "POST":
r.name = ActionSubmissionValidatedOperation
r.summary = "(Internal endpoint) Role Validator changes status from Validating -> Validated"
r.operationID = "actionSubmissionValidated"
r.pathPattern = "/submissions/{SubmissionID}/status/validator-validated"
r.args = args
r.count = 1
return r, true
default:
return
}
}
elem = origElem
}
elem = origElem
}
elem = origElem
}
elem = origElem
}
}
return r, false
}

View File

@ -0,0 +1,119 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"fmt"
)
func (s *ErrorStatusCode) Error() string {
return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response)
}
// ActionSubmissionReleasedNoContent is response for ActionSubmissionReleased operation.
type ActionSubmissionReleasedNoContent struct{}
// ActionSubmissionUploadedNoContent is response for ActionSubmissionUploaded operation.
type ActionSubmissionUploadedNoContent struct{}
// ActionSubmissionValidatedNoContent is response for ActionSubmissionValidated operation.
type ActionSubmissionValidatedNoContent struct{}
// Represents error object.
// Ref: #/components/schemas/Error
type Error struct {
Code int64 `json:"code"`
Message string `json:"message"`
}
// GetCode returns the value of Code.
func (s *Error) GetCode() int64 {
return s.Code
}
// GetMessage returns the value of Message.
func (s *Error) GetMessage() string {
return s.Message
}
// SetCode sets the value of Code.
func (s *Error) SetCode(val int64) {
s.Code = val
}
// SetMessage sets the value of Message.
func (s *Error) SetMessage(val string) {
s.Message = val
}
// ErrorStatusCode wraps Error with StatusCode.
type ErrorStatusCode struct {
StatusCode int
Response Error
}
// GetStatusCode returns the value of StatusCode.
func (s *ErrorStatusCode) GetStatusCode() int {
return s.StatusCode
}
// GetResponse returns the value of Response.
func (s *ErrorStatusCode) GetResponse() Error {
return s.Response
}
// SetStatusCode sets the value of StatusCode.
func (s *ErrorStatusCode) SetStatusCode(val int) {
s.StatusCode = val
}
// SetResponse sets the value of Response.
func (s *ErrorStatusCode) SetResponse(val Error) {
s.Response = val
}
// NewOptInt64 returns new OptInt64 with value set to v.
func NewOptInt64(v int64) OptInt64 {
return OptInt64{
Value: v,
Set: true,
}
}
// OptInt64 is optional int64.
type OptInt64 struct {
Value int64
Set bool
}
// IsSet returns true if OptInt64 was set.
func (o OptInt64) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptInt64) Reset() {
var v int64
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptInt64) SetTo(v int64) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptInt64) Get() (v int64, 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 OptInt64) Or(d int64) int64 {
if v, ok := o.Get(); ok {
return v
}
return d
}

View File

@ -0,0 +1,52 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
)
// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
// ActionSubmissionReleased implements actionSubmissionReleased operation.
//
// (Internal endpoint) Role Releaser changes status from releasing -> released.
//
// POST /submissions/{SubmissionID}/status/releaser-released
ActionSubmissionReleased(ctx context.Context, params ActionSubmissionReleasedParams) error
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
ActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) error
// ActionSubmissionValidated implements actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error
// NewError creates *ErrorStatusCode from error returned by handler.
//
// Used for common default response.
NewError(ctx context.Context, err error) *ErrorStatusCode
}
// Server implements http server based on OpenAPI v3 specification and
// calls Handler to handle requests.
type Server struct {
h Handler
baseServer
}
// NewServer creates new Server.
func NewServer(h Handler, opts ...ServerOption) (*Server, error) {
s, err := newServerConfig(opts...).baseServer()
if err != nil {
return nil, err
}
return &Server{
h: h,
baseServer: s,
}, nil
}

View File

@ -0,0 +1,49 @@
// Code generated by ogen, DO NOT EDIT.
package api
import (
"context"
ht "github.com/ogen-go/ogen/http"
)
// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented.
type UnimplementedHandler struct{}
var _ Handler = UnimplementedHandler{}
// ActionSubmissionReleased implements actionSubmissionReleased operation.
//
// (Internal endpoint) Role Releaser changes status from releasing -> released.
//
// POST /submissions/{SubmissionID}/status/releaser-released
func (UnimplementedHandler) ActionSubmissionReleased(ctx context.Context, params ActionSubmissionReleasedParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (UnimplementedHandler) ActionSubmissionUploaded(ctx context.Context, params ActionSubmissionUploadedParams) error {
return ht.ErrNotImplemented
}
// ActionSubmissionValidated implements actionSubmissionValidated operation.
//
// (Internal endpoint) Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validator-validated
func (UnimplementedHandler) ActionSubmissionValidated(ctx context.Context, params ActionSubmissionValidatedParams) error {
return ht.ErrNotImplemented
}
// NewError creates *ErrorStatusCode from error returned by handler.
//
// Used for common default response.
func (UnimplementedHandler) NewError(ctx context.Context, err error) (r *ErrorStatusCode) {
r = new(ErrorStatusCode)
return r
}

View File

@ -18,10 +18,7 @@ type PublishNewRequest struct {
SubmissionID int64 SubmissionID int64
ModelID uint64 ModelID uint64
ModelVersion uint64 ModelVersion uint64
Creator string ModelName string
DisplayName string
GameID uint32
//games HashSet<GameID>
} }
type PublishFixRequest struct { type PublishFixRequest struct {

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

@ -5,14 +5,18 @@ import "time"
type Status int32 type Status int32
const ( const (
StatusPublished Status = 8 // Phase: Final Status
StatusRejected Status = 7 StatusReleased Status = 9
StatusRejected Status = 8
StatusPublishing Status = 6 // Phase: Testing
StatusUploaded Status = 7 // uploaded to the group, but pending release
StatusUploading Status = 6
StatusValidated Status = 5 StatusValidated Status = 5
StatusValidating Status = 4 StatusValidating Status = 4
StatusAccepted Status = 3 StatusAccepted Status = 3
// Phase: Creation
StatusChangesRequested Status = 2 StatusChangesRequested Status = 2
StatusSubmitted Status = 1 StatusSubmitted Status = 1
StatusUnderConstruction Status = 0 StatusUnderConstruction Status = 0

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

@ -23,12 +23,14 @@ var (
type Roles struct { type Roles struct {
// human roles // human roles
SubmissionPublish bool SubmissionRelease bool
SubmissionReview bool SubmissionReview bool
ScriptWrite bool ScriptWrite bool
// Thumbnail bool
// MapDownload
// automated roles // automated roles
Maptest bool Maptest bool
Validator bool
} }
type UserInfo struct { type UserInfo struct {
@ -79,7 +81,7 @@ func (svc SecurityHandler) HandleCookieAuth(ctx context.Context, operationName a
// fix this when roblox udpates group roles // fix this when roblox udpates group roles
for _, r := range role.Roles { for _, r := range role.Roles {
if RoleMapAdmin <= r.Rank { if RoleMapAdmin <= r.Rank {
roles.SubmissionPublish = true roles.SubmissionRelease = true
} }
if RoleMapCouncil <= r.Rank { if RoleMapCouncil <= r.Rank {
roles.SubmissionReview = true roles.SubmissionReview = true

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() {
@ -158,20 +158,6 @@ func (svc *Service) UpdateSubmissionModel(ctx context.Context, params api.Update
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusChangesRequested, model.StatusSubmitted, model.StatusUnderConstruction}, pmap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusChangesRequested, model.StatusSubmitted, model.StatusUnderConstruction}, pmap)
} }
// ActionSubmissionPublish invokes actionSubmissionPublish operation.
//
// Role Validator changes status from Publishing -> Published.
//
// POST /submissions/{SubmissionID}/status/publish
func (svc *Service) ActionSubmissionPublish(ctx context.Context, params api.ActionSubmissionPublishParams) error {
println("[ActionSubmissionPublish] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusPublished)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusPublishing}, smap)
}
// ActionSubmissionReject invokes actionSubmissionReject operation. // ActionSubmissionReject invokes actionSubmissionReject operation.
// //
// Role Reviewer changes status from Submitted -> Rejected. // Role Reviewer changes status from Submitted -> Rejected.
@ -272,25 +258,25 @@ func (svc *Service) ActionSubmissionSubmit(ctx context.Context, params api.Actio
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction, model.StatusChangesRequested}, smap) return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUnderConstruction, model.StatusChangesRequested}, smap)
} }
// ActionSubmissionTriggerPublish invokes actionSubmissionTriggerPublish operation. // ActionSubmissionTriggerUpload invokes actionSubmissionTriggerUpload operation.
// //
// Role Admin changes status from Validated -> Publishing. // Role Admin changes status from Validated -> Uploading.
// //
// POST /submissions/{SubmissionID}/status/trigger-publish // POST /submissions/{SubmissionID}/status/trigger-upload
func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params api.ActionSubmissionTriggerPublishParams) error { func (svc *Service) ActionSubmissionTriggerUpload(ctx context.Context, params api.ActionSubmissionTriggerUploadParams) error {
userInfo, ok := ctx.Value("UserInfo").(UserInfo) userInfo, ok := ctx.Value("UserInfo").(UserInfo)
if !ok { if !ok {
return ErrUserInfo return ErrUserInfo
} }
// check if caller has required role // check if caller has required role
if !userInfo.Roles.SubmissionPublish { if !userInfo.Roles.SubmissionRelease {
return ErrPermissionDenied return ErrPermissionDenied
} }
// transaction // transaction
smap := datastore.Optional() smap := datastore.Optional()
smap.Add("status_id", model.StatusPublishing) smap.Add("status_id", model.StatusUploading)
submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, smap) submission, err := svc.DB.Submissions().IfStatusThenUpdateAndGet(ctx, params.SubmissionID, []model.Status{model.StatusValidated}, smap)
if err != nil { if err != nil {
return err return err
@ -303,9 +289,8 @@ func (svc *Service) ActionSubmissionTriggerPublish(ctx context.Context, params a
SubmissionID: submission.ID, SubmissionID: submission.ID,
ModelID: submission.AssetID, ModelID: submission.AssetID,
ModelVersion: submission.AssetVersion, ModelVersion: submission.AssetVersion,
Creator: submission.Creator, // publish as displayname, whatever
DisplayName: submission.DisplayName, ModelName: submission.DisplayName,
GameID: uint32(submission.GameID),
} }
j, err := json.Marshal(publish_new_request) j, err := json.Marshal(publish_new_request)
@ -374,17 +359,3 @@ func (svc *Service) ActionSubmissionTriggerValidate(ctx context.Context, params
return nil return nil
} }
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
//
// Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validate
func (svc *Service) ActionSubmissionValidate(ctx context.Context, params api.ActionSubmissionValidateParams) error {
println("[ActionSubmissionValidate] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
}

View File

@ -0,0 +1,25 @@
package service_internal
import (
"context"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"github.com/nats-io/nats.go"
)
type Service struct {
DB datastore.Datastore
Nats nats.JetStreamContext
}
// yay duplicate code
func (svc *Service) NewError(ctx context.Context, err error) *internal.ErrorStatusCode {
status := 500
return &internal.ErrorStatusCode{
StatusCode: status,
Response: internal.Error{
Code: int64(status),
Message: err.Error(),
},
}
}

View File

@ -0,0 +1,54 @@
package service_internal
import (
"context"
internal "git.itzana.me/strafesnet/maps-service/pkg/internal"
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
"git.itzana.me/strafesnet/maps-service/pkg/model"
)
// ActionSubmissionValidate invokes actionSubmissionValidate operation.
//
// Role Validator changes status from Validating -> Validated.
//
// POST /submissions/{SubmissionID}/status/validate
func (svc *Service) ActionSubmissionValidated(ctx context.Context, params internal.ActionSubmissionValidatedParams) error {
println("[ActionSubmissionValidate] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusValidated)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusValidating}, smap)
}
// ActionSubmissionReleased implements actionSubmissionReleased operation.
//
// (Internal endpoint) Role Releaser changes status from Uploaded -> Released.
//
// POST /submissions/{SubmissionID}/status/releaser-released
func (svc *Service) ActionSubmissionReleased(ctx context.Context, params internal.ActionSubmissionReleasedParams) error {
println("[ActionSubmissionReleased] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusReleased)
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUploaded}, smap)
}
// ActionSubmissionUploaded implements actionSubmissionUploaded operation.
//
// (Internal endpoint) Role Validator changes status from Uploading -> Uploaded.
//
// POST /submissions/{SubmissionID}/status/validator-uploaded
func (svc *Service) ActionSubmissionUploaded(ctx context.Context, params internal.ActionSubmissionUploadedParams) error {
println("[ActionSubmissionUploaded] Implicit Validator permission granted!")
// transaction
smap := datastore.Optional()
smap.Add("status_id", model.StatusUploaded)
if params.TargetAssetID.IsSet() {
smap.AddNotNil("target_asset_id", params.TargetAssetID.Value)
}
return svc.DB.Submissions().IfStatusThenUpdate(ctx, params.SubmissionID, []model.Status{model.StatusUploading}, smap)
}

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

1981
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.2.1", features = ["internal"], default-features = false, 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::internal::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,6 +1,6 @@
[package] [package]
name = "api" name = "submissions-api"
version = "0.1.0" version = "0.2.1"
edition = "2021" edition = "2021"
publish = ["strafesnet"] publish = ["strafesnet"]
repository = "https://git.itzana.me/StrafesNET/maps-service" repository = "https://git.itzana.me/StrafesNET/maps-service"
@ -15,3 +15,8 @@ reqwest = { version = "0", features = ["json"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
url = "2" url = "2"
[features]
default = ["external"]
internal = []
external = []

View File

@ -0,0 +1,23 @@
#[derive(Clone)]
pub struct Context{
pub base_url:String,
client:reqwest::Client,
}
impl Context{
pub fn new(mut base_url:String)->reqwest::Result<Self>{
base_url+="/v1";
Ok(Self{
base_url,
client:reqwest::Client::new(),
})
}
pub async fn get(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.get(url)
.send().await
}
pub async fn post(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.send().await
}
}

View File

@ -0,0 +1,86 @@
use crate::Error;
#[derive(serde::Deserialize)]
pub struct ScriptID(pub(crate) i64);
#[allow(nonstandard_style)]
pub struct GetScriptRequest{
pub ScriptID:ScriptID,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ScriptResponse{
pub ID:i64,
pub Hash:String,
pub Source:String,
pub SubmissionID:i64,
}
#[derive(serde::Deserialize)]
#[repr(i32)]
pub enum Policy{
None=0, // not yet reviewed
Allowed=1,
Blocked=2,
Delete=3,
Replace=4,
}
pub struct ScriptPolicyHashRequest{
pub hash:String,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ScriptPolicyResponse{
pub ID:i64,
pub FromScriptHash:String,
pub ToScriptID:ScriptID,
pub Policy:Policy
}
#[allow(nonstandard_style)]
pub struct UpdateSubmissionModelRequest{
pub SubmissionID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
}
#[derive(Clone)]
pub struct Context(crate::context::Context);
impl Context{
pub fn new(base_url:String)->reqwest::Result<Self>{
Ok(Self(crate::context::Context::new(base_url)?))
}
pub async fn get_script(&self,config:GetScriptRequest)->Result<ScriptResponse,Error>{
let url_raw=format!("{}/scripts/{}",self.0.base_url,config.ScriptID.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.0.get(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policy_from_hash(&self,config:ScriptPolicyHashRequest)->Result<ScriptPolicyResponse,Error>{
let url_raw=format!("{}/script-policy/hash/{}",self.0.base_url,config.hash);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.0.get(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?
.json().await.map_err(Error::Reqwest)
}
pub async fn update_submission_model(&self,config:UpdateSubmissionModelRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/model",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
{
url.query_pairs_mut()
.append_pair("ModelID",config.ModelID.to_string().as_str())
.append_pair("ModelVersion",config.ModelVersion.to_string().as_str());
}
self.0.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
}

View File

@ -0,0 +1,48 @@
use crate::Error;
#[allow(nonstandard_style)]
pub struct ActionSubmissionUploadedRequest{
pub SubmissionID:i64,
pub TargetAssetID:Option<u64>,
}
pub struct SubmissionID(pub i64);
#[derive(Clone)]
pub struct Context(crate::context::Context);
// there are lots of action endpoints and they all follow the same pattern
macro_rules! action{
($fname:ident,$action:expr)=>{
pub async fn $fname(&self,config:SubmissionID)->Result<(),Error>{
let url_raw=format!(concat!("{}/submissions/{}/status/",$action),self.0.base_url,config.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.0.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
};
}
impl Context{
pub fn new(base_url:String)->reqwest::Result<Self>{
Ok(Self(crate::context::Context::new(base_url)?))
}
pub async fn action_submission_uploaded(&self,config:ActionSubmissionUploadedRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/status/uploaded",self.0.base_url,config.SubmissionID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
if let Some(target_asset_id)=config.TargetAssetID{
url.query_pairs_mut()
.append_pair("TargetAssetID",target_asset_id.to_string().as_str());
}
self.0.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
action!(action_submission_validated,"validator-validated");
action!(action_submission_released,"releaser-released");
}

View File

@ -0,0 +1,22 @@
mod context;
#[cfg(feature="internal")]
pub mod internal;
#[cfg(feature="external")]
pub mod external;
//lazy reexport
pub type ReqwestError=reqwest::Error;
#[derive(Debug)]
pub enum Error{
ParseError(url::ParseError),
Reqwest(reqwest::Error),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}

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.2.1"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "b19f7aa92e0680fe206048851ca21761be56de415a5071f37c368c045a95f839"
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.2.1", features = ["internal","external"], 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

@ -1,130 +0,0 @@
#[derive(Debug)]
pub enum Error{
ParseError(url::ParseError),
Reqwest(reqwest::Error),
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
#[derive(serde::Deserialize)]
pub struct ScriptID(i64);
#[allow(nonstandard_style)]
pub struct GetScriptRequest{
pub ScriptID:ScriptID,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ScriptResponse{
pub ID:i64,
pub Hash:String,
pub Source:String,
pub SubmissionID:i64,
}
#[derive(serde::Deserialize)]
#[repr(i32)]
pub enum Policy{
Allowed=0,
Blocked=1,
Delete=2,
Replace=3,
}
pub struct ScriptPolicyHashRequest{
pub hash:String,
}
#[allow(nonstandard_style)]
#[derive(serde::Deserialize)]
pub struct ScriptPolicyResponse{
pub ID:i64,
pub FromScriptHash:String,
pub ToScriptID:ScriptID,
pub Policy:Policy
}
#[allow(nonstandard_style)]
pub struct UpdateSubmissionModelRequest{
pub ID:i64,
pub ModelID:u64,
pub ModelVersion:u64,
}
pub struct SubmissionID(pub i64);
#[derive(Clone)]
pub struct Context{
base_url:String,
client:reqwest::Client,
}
pub type ReqwestError=reqwest::Error;
// there are lots of action endpoints and they all follow the same pattern
macro_rules! action{
($fname:ident,$action:expr)=>{
pub async fn $fname(&self,config:SubmissionID)->Result<(),Error>{
let url_raw=format!(concat!("{}/submissions/{}/status/",$action),self.base_url,config.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
};
}
impl Context{
pub fn new(mut base_url:String)->reqwest::Result<Self>{
base_url+="/v1";
Ok(Self{
base_url,
client:reqwest::Client::new(),
})
}
async fn get(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.get(url)
.send().await
}
async fn post(&self,url:impl reqwest::IntoUrl)->Result<reqwest::Response,reqwest::Error>{
self.client.post(url)
.send().await
}
pub async fn get_script(&self,config:GetScriptRequest)->Result<ScriptResponse,Error>{
let url_raw=format!("{}/scripts/{}",self.base_url,config.ScriptID.0);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.get(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?
.json().await.map_err(Error::Reqwest)
}
pub async fn get_script_policy_from_hash(&self,config:ScriptPolicyHashRequest)->Result<ScriptPolicyResponse,Error>{
let url_raw=format!("{}/script-policy/hash/{}",self.base_url,config.hash);
let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
self.get(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?
.json().await.map_err(Error::Reqwest)
}
pub async fn update_submission_model(&self,config:UpdateSubmissionModelRequest)->Result<(),Error>{
let url_raw=format!("{}/submissions/{}/model",self.base_url,config.ID);
let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::ParseError)?;
{
url.query_pairs_mut()
.append_pair("ModelID",config.ModelID.to_string().as_str())
.append_pair("ModelVersion",config.ModelVersion.to_string().as_str());
}
self.post(url).await.map_err(Error::Reqwest)?
.error_for_status().map_err(Error::Reqwest)?;
Ok(())
}
action!(action_submission_validate,"validator-validated");
action!(action_submission_publish,"validator-published");
}

View File

@ -9,12 +9,12 @@ 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),
APIInternal(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 +23,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 +35,10 @@ 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::external::Context::new(api_host).map_err(StartupError::API)?;
let api_host_internal=std::env::var("API_HOST_INTERNAL").expect("API_HOST_INTERNAL env required");
let api_internal=submissions_api::internal::Context::new(api_host_internal).map_err(StartupError::APIInternal)?;
// 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 +56,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,api_internal);
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,13 +26,13 @@ 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::external::Context,
maps_grpc:crate::MapsServiceClient, api_internal:submissions_api::internal::Context,
)->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_internal.clone()),
publish_fix:crate::publish_fix::Publisher::new(cookie_context.clone(),api.clone()), publish_fix:crate::publish_fix::Publisher::new(cookie_context.clone(),api_internal.clone()),
validator:crate::validator::Validator::new(cookie_context,api), validator:crate::validator::Validator::new(cookie_context,api,api_internal),
} }
} }
pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{ pub async fn handle_message_result(&self,message_result:MessageResult)->Result<(),HandleMessageError>{

View File

@ -21,10 +21,7 @@ pub struct PublishNewRequest{
pub SubmissionID:i64, pub SubmissionID:i64,
pub ModelID:u64, pub ModelID:u64,
pub ModelVersion:u64, pub ModelVersion:u64,
pub Creator:String, pub ModelName:String,
pub DisplayName:String,
pub GameID:u32,
//games:HashSet<GameID>,
} }
#[allow(nonstandard_style)] #[allow(nonstandard_style)]

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), ApiActionSubmissionUploaded(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,16 +17,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_internal:submissions_api::internal::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_internal:submissions_api::internal::Context,
)->Self{ )->Self{
Self{ Self{
roblox_cookie, roblox_cookie,
api, api_internal,
} }
} }
pub async fn publish(&self,message:async_nats::jetstream::Message)->Result<(),PublishError>{ pub async fn publish(&self,message:async_nats::jetstream::Message)->Result<(),PublishError>{
@ -52,10 +52,11 @@ impl Publisher{
// that's it, the database entry does not need to be changed. // that's it, the database entry does not need to be changed.
// mark submission as published // mark submission as uploaded, TargetAssetID is unchanged
self.api.action_submission_publish( self.api_internal.action_submission_uploaded(submissions_api::internal::ActionSubmissionUploadedRequest{
api::SubmissionID(publish_info.SubmissionID) SubmissionID:publish_info.SubmissionID,
).await.map_err(PublishError::ApiActionSubmissionPublish)?; TargetAssetID:None,
}).await.map_err(PublishError::ApiActionSubmissionUploaded)?;
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::internal::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::internal::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>{
@ -47,36 +43,18 @@ impl Publisher{
// upload the map to the strafesnet group // upload the map to the strafesnet group
let upload_response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{ let upload_response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
name:publish_info.DisplayName.clone(), name:publish_info.ModelName.clone(),
description:"".to_owned(), description:"".to_owned(),
ispublic:false, ispublic:false,
allowComments:false, allowComments:false,
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 // note the asset id of the created model for later release, and mark the submission as uploaded
self.maps_grpc.clone().create(rust_grpc::maps::MapRequest{ self.api.action_submission_uploaded(submissions_api::internal::ActionSubmissionUploadedRequest{
id:upload_response.AssetId as i64, SubmissionID:publish_info.SubmissionID,
display_name:Some(publish_info.DisplayName), TargetAssetID:Some(upload_response.AssetId),
creator:Some(publish_info.Creator), }).await.map_err(PublishError::ApiActionSubmissionPublish)?;
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
self.api.action_submission_publish(
api::SubmissionID(publish_info.SubmissionID)
).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,17 +37,20 @@ 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::external::Context,
api_internal:submissions_api::internal::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::external::Context,
api_internal:submissions_api::internal::Context,
)->Self{ )->Self{
Self{ Self{
roblox_cookie, roblox_cookie,
api, api,
api_internal,
} }
} }
pub async fn validate(&self,message:async_nats::jetstream::Message)->Result<(),ValidateError>{ pub async fn validate(&self,message:async_nats::jetstream::Message)->Result<(),ValidateError>{
@ -71,7 +75,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 +89,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::external::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::external::Policy::None=>Policy::None,
api::Policy::Blocked=>Policy::Blocked, submissions_api::external::Policy::Allowed=>Policy::Allowed,
api::Policy::Delete=>Policy::Delete, submissions_api::external::Policy::Blocked=>Policy::Blocked,
api::Policy::Replace=>{ submissions_api::external::Policy::Delete=>Policy::Delete,
let script=self.api.get_script(api::GetScriptRequest{ submissions_api::external::Policy::Replace=>{
let script=self.api.get_script(submissions_api::external::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 +118,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,16 +169,16 @@ 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::external::UpdateSubmissionModelRequest{
ID:validate_info.SubmissionID, SubmissionID:validate_info.SubmissionID,
ModelID:model_id, ModelID:model_id,
ModelVersion:1, //TODO ModelVersion:1, //TODO
}).await.map_err(ValidateError::ApiUpdateSubmissionModel)?; }).await.map_err(ValidateError::ApiUpdateSubmissionModel)?;
}; };
// update the submission model status to validated // update the submission model status to validated
self.api.action_submission_validate( self.api_internal.action_submission_validated(
api::SubmissionID(validate_info.SubmissionID) submissions_api::internal::SubmissionID(validate_info.SubmissionID)
).await.map_err(ValidateError::ApiActionSubmissionValidate)?; ).await.map_err(ValidateError::ApiActionSubmissionValidate)?;
Ok(()) Ok(())

View File

@ -7,7 +7,7 @@ const nextConfig: NextConfig = {
return [ return [
{ {
source: "/v1/submissions/:submissionid/status/:statustype", source: "/v1/submissions/:submissionid/status/:statustype",
destination: "http://mapsservice:8082/v1/submissions/:submissionid/status/:statustype" destination: "http://submissions:8082/v1/submissions/:submissionid/status/:statustype"
} }
] ]
} }

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);
} }
} }
@ -51,4 +54,11 @@ 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,50 @@
"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 Radio from '@mui/material/Radio';
import "./(styles)/page.scss"
const enum Map {
New,
Fix,
}
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"/>
<FormControlLabel value="Fix" control={<Radio/>} label="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"/>
<TextField className="form-field" id="asset-id" label="Asset ID" variant="outlined"/>
<TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/>
<TargetAsset/>
</form>
<span className="spacer form-spacer"></span>
<Button variant="contained" startIcon={<SendIcon/>} 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
}