Add format query parameter
This commit is contained in:
41
src/cache.go
41
src/cache.go
@@ -1,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ResultCacheKey struct {
|
||||
@@ -14,21 +15,15 @@ type ResultCacheKey struct {
|
||||
}
|
||||
|
||||
// GetCacheKey returns the key used in the cache based on the rendering options, calculated as an SHA-256 hash.
|
||||
func GetResultCacheKey(uuid, renderType string, opts *QueryParams) (string, error) {
|
||||
rawKey := ResultCacheKey{
|
||||
UUID: uuid,
|
||||
Type: renderType,
|
||||
Scale: opts.Scale,
|
||||
Overlay: opts.Overlay,
|
||||
}
|
||||
func GetResultCacheKey(uuid, renderType string, opts *QueryParams) string {
|
||||
values := &url.Values{}
|
||||
values.Set("uuid", uuid)
|
||||
values.Set("type", renderType)
|
||||
values.Set("scale", strconv.FormatInt(int64(opts.Scale), 10))
|
||||
values.Set("overlay", strconv.FormatBool(opts.Overlay))
|
||||
values.Set("format", opts.Format)
|
||||
|
||||
rawKeyData, err := json.Marshal(rawKey)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("result:%s", SHA256(rawKeyData)), nil
|
||||
return SHA256(values.Encode())
|
||||
}
|
||||
|
||||
// GetCachedRenderResult returns the render result from Redis cache, or nil if it does not exist or cache is disabled.
|
||||
@@ -37,13 +32,7 @@ func GetCachedRenderResult(renderType, uuid string, opts *QueryParams) ([]byte,
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
key, err := GetResultCacheKey(uuid, renderType, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.GetBytes(key)
|
||||
return r.GetBytes(fmt.Sprintf("result:%s", GetResultCacheKey(uuid, renderType, opts)))
|
||||
}
|
||||
|
||||
// SetCachedRenderResult puts the render result into cache, or does nothing is cache is disabled.
|
||||
@@ -52,13 +41,7 @@ func SetCachedRenderResult(renderType, uuid string, opts *QueryParams, data []by
|
||||
return nil
|
||||
}
|
||||
|
||||
key, err := GetResultCacheKey(uuid, renderType, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Set(key, data, *config.Cache.RenderCacheDuration)
|
||||
return r.Set(fmt.Sprintf("result:%s", GetResultCacheKey(uuid, renderType, opts)), data, *config.Cache.RenderCacheDuration)
|
||||
}
|
||||
|
||||
// GetCachedSkin returns the raw skin of a player by UUID from the cache, also returning if the player has a slim player model.
|
||||
|
||||
@@ -21,6 +21,7 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
Head: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
@@ -28,6 +29,7 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
FullBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
@@ -35,6 +37,7 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
FrontBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
@@ -42,6 +45,7 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
BackBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
@@ -49,6 +53,7 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
LeftBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
@@ -56,6 +61,7 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
RightBody: RouteConfig{
|
||||
DefaultOverlay: true,
|
||||
@@ -63,9 +69,11 @@ var (
|
||||
DefaultScale: 4,
|
||||
MinScale: 1,
|
||||
MaxScale: 64,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
RawSkin: RouteConfig{
|
||||
DefaultDownload: false,
|
||||
DefaultFormat: "png",
|
||||
},
|
||||
},
|
||||
Cache: CacheConfig{
|
||||
@@ -100,11 +108,12 @@ type Routes struct {
|
||||
|
||||
// RouteConfig is the configuration data used by a single API route.
|
||||
type RouteConfig struct {
|
||||
DefaultScale int `yaml:"default_scale"`
|
||||
DefaultOverlay bool `yaml:"default_overlay"`
|
||||
DefaultDownload bool `yaml:"default_download"`
|
||||
MinScale int `yaml:"min_scale"`
|
||||
MaxScale int `yaml:"max_scale"`
|
||||
DefaultScale int `yaml:"default_scale"`
|
||||
DefaultOverlay bool `yaml:"default_overlay"`
|
||||
DefaultDownload bool `yaml:"default_download"`
|
||||
DefaultFormat string `yaml:"default_format"`
|
||||
MinScale int `yaml:"min_scale"`
|
||||
MaxScale int `yaml:"max_scale"`
|
||||
}
|
||||
|
||||
// CacheConfig is the configuration data used to set TTL values for Redis keys.
|
||||
|
||||
27
src/main.go
27
src/main.go
@@ -1,21 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
)
|
||||
|
||||
var (
|
||||
app *fiber.App = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
|
||||
log.Println(ctx.Request().URI(), err)
|
||||
var fiberError *fiber.Error
|
||||
|
||||
if errors.As(err, &fiberError) {
|
||||
return ctx.SendStatus(fiberError.Code)
|
||||
}
|
||||
|
||||
log.Printf("Error: %v - URI: %s\n", err, ctx.Request().URI())
|
||||
|
||||
return ctx.SendStatus(http.StatusInternalServerError)
|
||||
},
|
||||
@@ -38,21 +42,6 @@ func init() {
|
||||
|
||||
log.Println("Successfully connected to Redis")
|
||||
|
||||
app.Use(recover.New())
|
||||
|
||||
if config.Environment == "development" {
|
||||
app.Use(cors.New(cors.Config{
|
||||
AllowOrigins: "*",
|
||||
AllowMethods: "HEAD,OPTIONS,GET",
|
||||
ExposeHeaders: "X-Cache-Hit,X-Cache-Time-Remaining",
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: "${time} ${ip}:${port} -> ${status}: ${method} ${path} (${latency})\n",
|
||||
TimeFormat: "2006/01/02 15:04:05",
|
||||
}))
|
||||
}
|
||||
|
||||
if instanceID, err = GetInstanceID(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func Render(renderType, uuid string, rawSkin *image.NRGBA, isSlim bool, opts *Qu
|
||||
|
||||
// Encode the image into a PNG in byte-array format
|
||||
{
|
||||
data, err = EncodePNG(result)
|
||||
data, err = EncodeImage(result, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
||||
137
src/routes.go
137
src/routes.go
@@ -5,22 +5,35 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
lastCount uint64 = 0
|
||||
lastCountRetrievedAt *time.Time = nil
|
||||
lastCountMutex *sync.Mutex = &sync.Mutex{}
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/favicon"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
)
|
||||
|
||||
func init() {
|
||||
app.Use(recover.New())
|
||||
|
||||
app.Use(favicon.New(favicon.Config{
|
||||
Data: faviconData,
|
||||
}))
|
||||
|
||||
if config.Environment == "development" {
|
||||
app.Use(cors.New(cors.Config{
|
||||
AllowOrigins: "*",
|
||||
AllowMethods: "HEAD,OPTIONS,GET",
|
||||
ExposeHeaders: "X-Cache-Hit,X-Cache-Time-Remaining",
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: "${time} ${ip}:${port} -> ${status}: ${method} ${path} (${latency})\n",
|
||||
TimeFormat: "2006/01/02 15:04:05",
|
||||
}))
|
||||
}
|
||||
|
||||
app.Get("/ping", PingHandler)
|
||||
app.Get("/favicon.ico", FaviconHandler)
|
||||
app.Get("/count", CountHandler)
|
||||
app.Get("/list", ListHandler)
|
||||
app.Get("/skin/:uuid", SkinHandler)
|
||||
app.Get("/face/:uuid", FaceHandler)
|
||||
@@ -30,11 +43,6 @@ func init() {
|
||||
app.Get("/body/back/:uuid", BackBodyHandler)
|
||||
app.Get("/body/left/:uuid", LeftBodyHandler)
|
||||
app.Get("/body/right/:uuid", RightBodyHandler)
|
||||
app.Use(NotFoundHandler)
|
||||
}
|
||||
|
||||
type CountResponse struct {
|
||||
Count uint64 `json:"count"`
|
||||
}
|
||||
|
||||
// PingHandler is the API handler used for the `/ping` route.
|
||||
@@ -42,48 +50,6 @@ func PingHandler(ctx *fiber.Ctx) error {
|
||||
return ctx.SendStatus(http.StatusOK)
|
||||
}
|
||||
|
||||
// FaviconHandler serves the favicon.ico file to any users that visit the API using a browser.
|
||||
func FaviconHandler(ctx *fiber.Ctx) error {
|
||||
return ctx.Type("ico").Send(favicon)
|
||||
}
|
||||
|
||||
// CountHandler is the API handler used for the `/count` route.
|
||||
func CountHandler(ctx *fiber.Ctx) error {
|
||||
lastCountMutex.Lock()
|
||||
|
||||
defer lastCountMutex.Unlock()
|
||||
|
||||
if lastCountRetrievedAt == nil || time.Since(*lastCountRetrievedAt) > time.Minute*15 {
|
||||
lastCount = 0
|
||||
|
||||
var (
|
||||
keys []string
|
||||
cursor uint64 = 0
|
||||
err error
|
||||
)
|
||||
|
||||
for {
|
||||
keys, cursor, err = r.Scan(cursor, "unique:*", 25)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastCount += uint64(len(keys))
|
||||
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
lastCountRetrievedAt = PointerOf(time.Now())
|
||||
}
|
||||
|
||||
return ctx.JSON(CountResponse{
|
||||
Count: lastCount,
|
||||
})
|
||||
}
|
||||
|
||||
// ListHandler is the API handler used for the `/list` route.
|
||||
func ListHandler(ctx *fiber.Ctx) error {
|
||||
result := make([]string, 0)
|
||||
@@ -117,6 +83,10 @@ func ListHandler(ctx *fiber.Ctx) error {
|
||||
func SkinHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.RawSkin)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ctx.Params("uuid"))
|
||||
|
||||
if !ok {
|
||||
@@ -129,23 +99,27 @@ func SkinHandler(ctx *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := EncodePNG(rawSkin)
|
||||
data, err := EncodeImage(rawSkin, opts)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Download {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.%s"`, uuid, opts.Format))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(data)
|
||||
return ctx.Type(opts.Format).Send(data)
|
||||
}
|
||||
|
||||
// FaceHandler is the API handler used for the `/face/:uuid` route.
|
||||
func FaceHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.Face)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -170,13 +144,17 @@ func FaceHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
// HeadHandler is the API handler used for the `/head/:uuid` route.
|
||||
func HeadHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.Head)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -201,13 +179,17 @@ func HeadHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
// FullBodyHandler is the API handler used for the `/body/full/:uuid` route.
|
||||
func FullBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.FullBody)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -232,13 +214,17 @@ func FullBodyHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
// FrontBodyHandler is the API handler used for the `/body/front/:uuid` route.
|
||||
func FrontBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.FrontBody)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -263,13 +249,17 @@ func FrontBodyHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
// BackBodyHandler is the API handler used for the `/body/back/:uuid` route.
|
||||
func BackBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.BackBody)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -294,13 +284,17 @@ func BackBodyHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
// LeftBodyHandler is the API handler used for the `/body/left/:uuid` route.
|
||||
func LeftBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.LeftBody)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -325,13 +319,17 @@ func LeftBodyHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
// RightBodyHandler is the API handler used for the `/body/right/:uuid` route.
|
||||
func RightBodyHandler(ctx *fiber.Ctx) error {
|
||||
opts := ParseQueryParams(ctx, config.Routes.RightBody)
|
||||
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid, ok := ParseUUID(ExtractUUID(ctx))
|
||||
|
||||
if !ok {
|
||||
@@ -356,10 +354,5 @@ func RightBodyHandler(ctx *fiber.Ctx) error {
|
||||
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, uuid))
|
||||
}
|
||||
|
||||
return ctx.Type("png").Send(result)
|
||||
}
|
||||
|
||||
// NotFoundHandler is the API handler used for any requests that do not match an existing route.
|
||||
func NotFoundHandler(ctx *fiber.Ctx) error {
|
||||
return ctx.SendStatus(http.StatusNotFound)
|
||||
return ctx.Type(opts.Format).Send(result)
|
||||
}
|
||||
|
||||
101
src/util.go
101
src/util.go
@@ -8,6 +8,8 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -21,14 +23,21 @@ import (
|
||||
|
||||
var (
|
||||
//go:embed favicon.ico
|
||||
favicon []byte
|
||||
faviconData []byte
|
||||
AllowedFormats []string = []string{
|
||||
"png",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"gif",
|
||||
}
|
||||
)
|
||||
|
||||
// QueryParams is used by most all API routes as options for how the image should be rendered, or how errors should be handled.
|
||||
type QueryParams struct {
|
||||
Scale int `query:"scale"`
|
||||
Download bool `query:"download"`
|
||||
Overlay bool `query:"overlay"`
|
||||
Scale int
|
||||
Download bool
|
||||
Overlay bool
|
||||
Format string
|
||||
}
|
||||
|
||||
// PointerOf returns the value of the first argument as a pointer.
|
||||
@@ -36,6 +45,19 @@ func PointerOf[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Contains returns true if the array contains the value.
|
||||
func Contains[T comparable](arr []T, value T) bool {
|
||||
for _, v := range arr {
|
||||
if v != value {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Clamp clamps the input value between the minimum and maximum values.
|
||||
// This method is preferred over `math.Min()` and `math.Max()` to prevent any type coercion between floats and integers.
|
||||
func Clamp[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](value, min, max T) T {
|
||||
@@ -200,31 +222,58 @@ func EncodePNG(img image.Image) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// EncodeImage encodes the image into the format specified by the query parameters.
|
||||
func EncodeImage(img image.Image, opts *QueryParams) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
switch opts.Format {
|
||||
case "png":
|
||||
{
|
||||
if err := png.Encode(buf, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case "jpg", "jpeg":
|
||||
{
|
||||
if err := jpeg.Encode(buf, img, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case "gif":
|
||||
{
|
||||
if err := gif.Encode(buf, img, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid format: %s", opts.Format)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ParseQueryParams parses the query parameters from the request and returns a QueryParams struct, using default values from the provided configuration.
|
||||
func ParseQueryParams(ctx *fiber.Ctx, route RouteConfig) *QueryParams {
|
||||
args := ctx.Context().QueryArgs()
|
||||
format := ctx.Query("format", route.DefaultFormat)
|
||||
|
||||
response := &QueryParams{
|
||||
Scale: route.DefaultScale,
|
||||
Download: route.DefaultDownload,
|
||||
Overlay: route.DefaultOverlay,
|
||||
if !Contains(AllowedFormats, format) {
|
||||
ctx.Status(http.StatusBadRequest).SendString("Invalid 'format' query parameter")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if args.Has("scale") {
|
||||
if scale, err := args.GetUint("scale"); err == nil {
|
||||
response.Scale = Clamp(scale, route.MinScale, route.MaxScale)
|
||||
}
|
||||
return &QueryParams{
|
||||
Scale: Clamp(ctx.QueryInt("scale", route.DefaultScale), route.MinScale, route.MaxScale),
|
||||
Download: ctx.QueryBool("download", route.DefaultDownload),
|
||||
Overlay: ctx.QueryBool("overlay", route.DefaultOverlay),
|
||||
Format: ctx.Query("format", route.DefaultFormat),
|
||||
}
|
||||
|
||||
if args.Has("overlay") {
|
||||
response.Overlay = args.GetBool("overlay")
|
||||
}
|
||||
|
||||
if args.Has("download") {
|
||||
response.Download = args.GetBool("download")
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// GetInstanceID returns the INSTANCE_ID environment variable parsed as an unsigned 16-bit integer.
|
||||
@@ -242,9 +291,9 @@ func GetInstanceID() (uint16, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// SHA256 computes the SHA-256 hash of the input byte-array.
|
||||
func SHA256(value []byte) string {
|
||||
hash := sha256.Sum256(value)
|
||||
// SHA256 computes the SHA-256 hash of the input string.
|
||||
func SHA256(value string) string {
|
||||
hash := sha256.Sum256([]byte(value))
|
||||
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user