mirror of
https://github.com/elyby/chrly.git
synced 2025-05-31 14:11:51 +05:30
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfe8fea3f7 | ||
|
|
27c7b79b32 | ||
|
|
ad31fdb709 | ||
|
|
fa62d45d00 | ||
|
|
cadb89f00a | ||
|
|
20ba78953b | ||
|
|
883a7bda3c | ||
|
|
d678f61df7 | ||
|
|
1543e98b87 | ||
|
|
0e0b41d6d7 | ||
|
|
3cd12acc1b | ||
|
|
dac4ed0ac6 | ||
|
|
11a779c670 | ||
|
|
6cb5e1eb42 | ||
|
|
8959e53270 | ||
|
|
3526570dd3 | ||
|
|
4b7f1346f5 | ||
|
|
e7721f9e5a | ||
|
|
26bfbd1517 | ||
|
|
ecbb06b83c | ||
|
|
6accffed45 |
13
.fossa.yml
13
.fossa.yml
@@ -1,13 +0,0 @@
|
||||
version: 2
|
||||
cli:
|
||||
server: https://app.fossa.com
|
||||
fetcher: git
|
||||
project: github.com:elyby/chrly
|
||||
analyze:
|
||||
modules:
|
||||
- name: chrly
|
||||
type: go
|
||||
target: github.com/elyby/chrly
|
||||
path: .
|
||||
options:
|
||||
strategy: dep
|
||||
53
.github/workflows/build.yml
vendored
Normal file
53
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags:
|
||||
- '*.*.*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
cache-dependency-path: go.sum
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install dependencies
|
||||
run: go get .
|
||||
|
||||
- name: Go Format
|
||||
run: gofmt -s -w . && git diff --exit-code
|
||||
|
||||
- name: Go Vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Go Test
|
||||
run: go test -v -race --tags redis -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4-beta
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
run: go build ./...
|
||||
66
.github/workflows/release.yml
vendored
Normal file
66
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Build
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
dockerhub:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- id: meta
|
||||
name: Docker meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ github.repository }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=edge,branch=${{ github.event.repository.default_branch }}
|
||||
|
||||
- id: version
|
||||
name: Set up build version
|
||||
run: |
|
||||
if [[ $GITHUB_REF_TYPE == "tag" ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
else
|
||||
BRANCH_NAME=${GITHUB_REF#refs/heads/}
|
||||
SHORT_SHA=$(git rev-parse --short $GITHUB_SHA)
|
||||
VERSION="${BRANCH_NAME}-${SHORT_SHA}"
|
||||
fi
|
||||
echo "### Version: $VERSION" >> $GITHUB_STEP_SUMMARY
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
COMMIT=${{ github.sha }}
|
||||
66
.travis.yml
66
.travis.yml
@@ -1,66 +0,0 @@
|
||||
os: linux
|
||||
dist: xenial
|
||||
language: go
|
||||
go:
|
||||
- "1.14"
|
||||
|
||||
stages:
|
||||
- Tests
|
||||
- name: Deploy
|
||||
if: env(TRAVIS_PULL_REQUEST) IS false AND (branch = master OR tag IS present) AND commit_message !~ /(\[skip deploy\])/
|
||||
|
||||
install:
|
||||
- go get -u github.com/golang/dep/cmd/dep
|
||||
- dep ensure
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/pkg/dep
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# Tests stage
|
||||
- name: Unit tests
|
||||
stage: Tests
|
||||
services:
|
||||
- redis
|
||||
script:
|
||||
- go test -v -race --tags redis -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
- name: FOSSA
|
||||
stage: Tests
|
||||
if: branch = master
|
||||
before_script:
|
||||
- curl https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash
|
||||
script:
|
||||
- fossa init
|
||||
- fossa analyze
|
||||
# Disable until https://github.com/fossas/fossa-cli/issues/596 will be resolved
|
||||
# - fossa test
|
||||
|
||||
# Deploy stage
|
||||
- name: Docker image
|
||||
stage: Deploy
|
||||
services:
|
||||
- docker
|
||||
script:
|
||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
|
||||
- export DOCKER_TAG="${TRAVIS_TAG:-dev}"
|
||||
- export APP_VERSION="${TRAVIS_TAG:-dev-${TRAVIS_COMMIT:0:7}}"
|
||||
- export BUILD_TAGS=""
|
||||
- if [ "$DOCKER_TAG" == "dev" ]; then export BUILD_TAGS="$BUILD_TAGS --tags profiling"; fi
|
||||
- >
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||
go build
|
||||
$BUILD_TAGS
|
||||
-o release/chrly
|
||||
-ldflags "-extldflags '-static' -X github.com/elyby/chrly/version.version=$APP_VERSION -X github.com/elyby/chrly/version.commit=$TRAVIS_COMMIT"
|
||||
main.go
|
||||
- docker build -t elyby/chrly:$DOCKER_TAG .
|
||||
- docker push elyby/chrly:$DOCKER_TAG
|
||||
- |
|
||||
if [ ! -z ${TRAVIS_TAG:+x} ]; then
|
||||
docker tag elyby/chrly:$DOCKER_TAG elyby/chrly:latest
|
||||
docker push elyby/chrly:latest
|
||||
fi
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -5,6 +5,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased] - xxxx-xx-xx
|
||||
### Added
|
||||
- Allow to remove a skin without removing all user information
|
||||
- New StatsD metrics:
|
||||
- Counters:
|
||||
- `ely.skinsystem.{hostname}.app.profiles.request`
|
||||
|
||||
### Fixed
|
||||
- Adjusted Mojang usernames filter to be stickier according to their docs
|
||||
- `/profile/{username}` endpoint now returns the correct signature for the custom property as well.
|
||||
|
||||
### Changed
|
||||
- Bumped Go version to 1.21.
|
||||
|
||||
### Removed
|
||||
- Removed mentioning and processing of skin uploading as a file, as this functionality was never implemented and was not planned to be implemented
|
||||
- StatsD metrics:
|
||||
- Gauges:
|
||||
- `ely.skinsystem.{hostname}.app.redis.pool.available`
|
||||
|
||||
## [4.6.0] - 2021-03-04
|
||||
### Added
|
||||
|
||||
29
Dockerfile
29
Dockerfile
@@ -1,14 +1,29 @@
|
||||
FROM alpine:3.9.3
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
ARG VERSION=unversioned
|
||||
ARG COMMIT=unspecified
|
||||
|
||||
COPY . /build
|
||||
WORKDIR /build
|
||||
RUN go mod download
|
||||
|
||||
RUN CGO_ENABLED=0 \
|
||||
go build \
|
||||
-trimpath \
|
||||
-ldflags "-w -s -X github.com/elyby/chrly/version.version=$VERSION -X github.com/elyby/chrly/version.commit=$COMMIT" \
|
||||
-o chrly \
|
||||
main.go
|
||||
|
||||
FROM alpine:3.19
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
ENV STORAGE_REDIS_HOST=redis
|
||||
ENV STORAGE_FILESYSTEM_HOST=/data
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
COPY release/chrly /usr/local/bin/
|
||||
COPY docker-entrypoint.sh /
|
||||
COPY --from=builder /build/chrly /usr/local/bin/chrly
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["serve"]
|
||||
|
||||
350
Gopkg.lock
generated
350
Gopkg.lock
generated
@@ -1,350 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8855efc2aff3afd6319da41b22a8ca1cfd1698af05a24852c01636ba65b133f0"
|
||||
name = "github.com/SermoDigital/jose"
|
||||
packages = [
|
||||
".",
|
||||
"crypto",
|
||||
"jws",
|
||||
"jwt",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "f6df55f235c24f236d11dbcf665249a59ac2021f"
|
||||
version = "1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "publish_nil_values"
|
||||
digest = "1:d02c8323070a3d8d8ca039d0d180198ead0a75eac4fb1003af812435a2b391e8"
|
||||
name = "github.com/asaskevich/EventBus"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "33b3bc6a7ddca2f99683c5c3ee86b24f80a7a075"
|
||||
source = "https://github.com/erickskrauch/EventBus.git"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c7b11da9bf0707e6920e1b361fbbbbe9b277ef3a198377baa4527f6e31049be0"
|
||||
name = "github.com/certifi/gocertifi"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3fd9e1adb12b72d2f3f82191d49be9b93c69f67c"
|
||||
version = "2017.07.27"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = ""
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2e7c296138d042515eb2995fe58026eaef2c08f660a5f36584faecf34eea3cf0"
|
||||
name = "github.com/etherlabsio/healthcheck"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "dd3d2fd8c3f620a32b7f3cd9b4f0d2f7d0875ab1"
|
||||
version = "2.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9f1e571696860f2b4f8a241b43ce91c6085e7aaed849ccca53f590a4dc7b95bd"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "629574ca2a5df945712d3079857300b5e4da0236"
|
||||
version = "v1.4.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:904b0b847f705de43c15e6c8f3dd639044db5601dedfb2f3fdb3021a28491d15"
|
||||
name = "github.com/getsentry/raven-go"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "919484f041ea21e7e27be291cee1d6af7bc98864"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8c9f13aac9e92f3754ea591b39ada87b9f89f1e75c4b90ccbd0b1084069c436f"
|
||||
name = "github.com/goava/di"
|
||||
packages = [
|
||||
".",
|
||||
"internal/reflection",
|
||||
"internal/stacktrace",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "1eb6eb721bf050edff0efbf15c31636def701b4b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:65c7ed49d9f36dd4752e43013323fa9229db60b29aa4f5a75aaecda3130c74e2"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "c5c6c98bc25355028a63748a498942a6398ccd22"
|
||||
version = "v1.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0f31ddb2589297fc1d716f45b34e34bff34e968de1aa239543274c87522e86f4"
|
||||
name = "github.com/h2non/parth"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "b4df798d65426f8c8ab5ca5f9987aec5575d26c9"
|
||||
version = "v2.0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8017a99c7fa4dac90c7a34e08d5f890043fc27e91e561f3267a09f65595b158c"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "8f6b1344a92ff8877cf24a5de9177bf7d0a2a187"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1ce378ab2352c756c6d7a0172c22ecbd387659d32712a4ce3bc474273309a5dc"
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "be5ece7dd465ab0765a9682137865547526d1dfb"
|
||||
version = "v1.7.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:19a9f4143462f07553e9bf6ae0f1b8633a2c44763b1df90d4e9e49f51cd8423a"
|
||||
name = "github.com/mediocregopher/radix.v2"
|
||||
packages = [
|
||||
"cluster",
|
||||
"pool",
|
||||
"redis",
|
||||
"util",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "b67df6e626f993b64b3ca9f4b8630900e61002e3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c9ede10a9ded782d25d1f0be87c680e11409c23554828f19a19d691a95e76130"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "d0303fe809921458f417bcf828397a65db30a7e4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c62e653e0a78bcf08fd56c764e5725e604693ffbd35b2b283b360f174d073a75"
|
||||
name = "github.com/mono83/slf"
|
||||
packages = [
|
||||
".",
|
||||
"filters",
|
||||
"params",
|
||||
"rays",
|
||||
"recievers",
|
||||
"recievers/sentry",
|
||||
"recievers/statsd",
|
||||
"recievers/writer",
|
||||
"wd",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "79153e9636db86e1c6b74d74dd04176f257a4f2d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:270261c28f6e71a8a31b9d308ec9145147040fd80d21563307767a8e844bfabc"
|
||||
name = "github.com/mono83/udpwriter"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "a064bd7e3acfda563ea680b913b9ef24b7a73e15"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:049b5bee78dfdc9628ee0e557219c41f683e5b06c5a5f20eaba0105ccc586689"
|
||||
name = "github.com/pelletier/go-buffruneio"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "c37440a7cf42ac63b919c752ca73a85067e05992"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6d5a9728ae27e477a07bb69f02ea0bade74eb8b0c7346d046337904bbf7af065"
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "5ccdfb18c776b740aecaf085c4d9a2779199c279"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = ""
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c189f11a84aa8b868a4b7cd4605653160424ab299cf7cfb1c5bd2740b949928f"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "ee1bd8ee15a1306d1f9201acc41ef39cd9f99a1b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6ff9b74bfea2625f805edec59395dc37e4a06458dd3c14e3372337e3d35a2ed6"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5cb42b990db5dc48b8bc23b6ee77b260713ba3244ca495cd1ed89533dc482a49"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "12bd96e66386c1960ab0f74ced1362f66f552f7b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cbaf13cdbfef0e4734ed8a7504f57fe893d471d62a35b982bf6fb3f036449a66"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
|
||||
version = "v1.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:90fe60ab6f827e308b0c8cc1e11dce8ff1e96a927c8b171271a3cb04dd517606"
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:711eebe744c0151a9d09af2315f0bb729b2ec7637ef4c410fa90a18ef74b65b6"
|
||||
name = "github.com/stretchr/objx"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cc4eb6813da8d08694e557fcafae8fcc24f47f61a0717f952da130ca9a486dfc"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
"mock",
|
||||
"require",
|
||||
"suite",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3ebf1ddaeb260c4b1ae502a01c7844fa8c1fa0e9"
|
||||
version = "v1.5.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:061754b9de261d8e1cf804970dff7b3e105d1cb4883ef446dbe911489ba8e9eb"
|
||||
name = "github.com/thedevsaddam/govalidator"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "0413a0eb80cac8ab2d666639130658ce49a0c967"
|
||||
version = "v1.9.6"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:19adc71218d62052cd18b83ebaab77961378876094081f4b1581ca28ef80395d"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
pruneopts = ""
|
||||
revision = "7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:653926785eac385fd1d61dc16360a5194c68d4bf2541234363a9375d2e88a039"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"internal/gen",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"transform",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "bd91bbf73e9a4a801adbfb97133c992678533126"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a7fe9cf6ed8bca3f0ef319592db1621ce244e7ffefba3c2c21ee40d6898a9c8"
|
||||
name = "gopkg.in/h2non/gock.v1"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "3ffff9b1aa8200275a5eb219c5f9c62bd27acb31"
|
||||
version = "v1.0.15"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
digest = "1:81314a486195626940617e43740b4fa073f265b0715c9f54ce2027fee1cb5f61"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/SermoDigital/jose/crypto",
|
||||
"github.com/SermoDigital/jose/jws",
|
||||
"github.com/asaskevich/EventBus",
|
||||
"github.com/etherlabsio/healthcheck",
|
||||
"github.com/getsentry/raven-go",
|
||||
"github.com/goava/di",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/mediocregopher/radix.v2/pool",
|
||||
"github.com/mediocregopher/radix.v2/redis",
|
||||
"github.com/mediocregopher/radix.v2/util",
|
||||
"github.com/mono83/slf",
|
||||
"github.com/mono83/slf/params",
|
||||
"github.com/mono83/slf/rays",
|
||||
"github.com/mono83/slf/recievers/sentry",
|
||||
"github.com/mono83/slf/recievers/statsd",
|
||||
"github.com/mono83/slf/recievers/writer",
|
||||
"github.com/mono83/slf/wd",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/spf13/viper",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/mock",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/stretchr/testify/suite",
|
||||
"github.com/thedevsaddam/govalidator",
|
||||
"gopkg.in/h2non/gock.v1",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
56
Gopkg.toml
56
Gopkg.toml
@@ -1,56 +0,0 @@
|
||||
ignored = ["github.com/elyby/chrly"]
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "^1.6.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mediocregopher/radix.v2"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mono83/slf"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "^0.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/viper"
|
||||
version = "^1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/getsentry/raven-go"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/SermoDigital/jose"
|
||||
version = "~1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/thedevsaddam/govalidator"
|
||||
version = "^1.9.6"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/asaskevich/EventBus"
|
||||
source = "https://github.com/erickskrauch/EventBus.git"
|
||||
branch = "publish_nil_values"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/etherlabsio/healthcheck"
|
||||
version = "2.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/goava/di"
|
||||
branch = "master"
|
||||
|
||||
# Testing dependencies
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "^1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/h2non/gock.v1"
|
||||
version = "^1.0.15"
|
||||
2
LICENSE
2
LICENSE
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Ely.by (http://ely.by)
|
||||
Copyright 2023 Ely.by (https://ely.by)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
37
README.md
37
README.md
@@ -5,7 +5,6 @@
|
||||
[![Coverage][ico-coverage]][link-coverage]
|
||||
[![Keep a Changelog][ico-changelog]](CHANGELOG.md)
|
||||
[![Software License][ico-license]](LICENSE)
|
||||
[![FOSSA Status][ico-fossa]][link-fossa]
|
||||
|
||||
Chrly is a lightweight implementation of Minecraft skins system server with ability to proxy requests to Mojang's
|
||||
skins system. It's packaged and distributed as a Docker image and can be downloaded from
|
||||
@@ -244,11 +243,12 @@ Response example:
|
||||
"properties": [
|
||||
{
|
||||
"name": "textures",
|
||||
"signature": "signature value",
|
||||
"signature": "textures signature value",
|
||||
"value": "base64 encoded value"
|
||||
},
|
||||
{
|
||||
"name": "chrly",
|
||||
"signature": "custom property signature value",
|
||||
"value": "how do you tame a horse in Minecraft?"
|
||||
}
|
||||
]
|
||||
@@ -345,10 +345,9 @@ You can obtain token by executing `docker-compose run --rm app token`.
|
||||
|
||||
#### `POST /api/skins`
|
||||
|
||||
> **Warning**: skin uploading via `skin` field is not implemented for now.
|
||||
Endpoint allows you to create or update skin record for a username.
|
||||
|
||||
Endpoint allows you to create or update skin record for a username. To upload skin, you have to send multipart
|
||||
form data. `form-urlencoded` also supported, but, as you may know, it doesn't support files uploading.
|
||||
The request body must be encoded as `application/x-www-form-urlencoded`.
|
||||
|
||||
**Request params:**
|
||||
|
||||
@@ -362,8 +361,9 @@ form data. `form-urlencoded` also supported, but, as you may know, it doesn't su
|
||||
| isSlim | bool | Does skin have slim arms (Alex model). |
|
||||
| mojangTextures | string | Mojang textures field. It must be a base64 encoded json string. Not required. |
|
||||
| mojangSignature | string | Signature for Mojang textures, which is required when `mojangTextures` passed. |
|
||||
| url | string | Actual url of the skin. You have to pass this parameter or `skin`. |
|
||||
| skin | file | Skin file. You have to pass this parameter or `url`. |
|
||||
| url | string | Actual url of the skin. |
|
||||
|
||||
**Important**: all parameters are always read at least as their default values. So, if you only want to update the username and not pass the skin data it will reset all skin information. If you want to keep the data, you should always pass the full set of parameters.
|
||||
|
||||
If successful you'll receive `201` status code. In the case of failure there will be `400` status code and errors list
|
||||
as json:
|
||||
@@ -454,18 +454,15 @@ If any of the checks fails, the server will return `503` status code with the fo
|
||||
First of all you should install the [latest stable version of Go](https://golang.org/doc/install) and set `GOPATH`
|
||||
environment variable.
|
||||
|
||||
This project uses [`dep`](https://github.com/golang/dep) for dependencies management, so it
|
||||
[should be installed](https://github.com/golang/dep#installation) too.
|
||||
|
||||
Then you must fork this repository. Now follow these steps:
|
||||
|
||||
```sh
|
||||
# Get the source code
|
||||
go get github.com/elyby/chrly
|
||||
git clone https://github.com/elyby/chrly.git
|
||||
# Switch to the project folder
|
||||
cd $GOPATH/src/github.com/elyby/chrly
|
||||
# Install dependencies (it can take a while)
|
||||
dep ensure
|
||||
cd chrly
|
||||
# Install dependencies
|
||||
go mod download
|
||||
# Add your fork link as a remote
|
||||
git remote add fork git@github.com:your-username/chrly.git
|
||||
# Create a new branch for your task
|
||||
@@ -493,18 +490,12 @@ If your Redis instance isn't located at the `localhost`, you can change host by
|
||||
After all of that `go run main.go serve` should successfully start the application.
|
||||
To run tests execute `go test ./...`.
|
||||
|
||||
## License
|
||||
[![FOSSA Status][ico-fossa-big]][link-fossa]
|
||||
|
||||
[ico-lang]: https://img.shields.io/badge/lang-go%201.14-blue.svg?style=flat-square
|
||||
[ico-build]: https://img.shields.io/travis/elyby/chrly.svg?style=flat-square
|
||||
[ico-lang]: https://img.shields.io/github/go-mod/go-version/elyby/chrly?style=flat-square
|
||||
[ico-build]: https://img.shields.io/github/actions/workflow/status/elyby/chrly/build.yml?style=flat-square
|
||||
[ico-coverage]: https://img.shields.io/codecov/c/github/elyby/chrly.svg?style=flat-square
|
||||
[ico-changelog]: https://img.shields.io/badge/keep%20a-changelog-orange.svg?style=flat-square
|
||||
[ico-license]: https://img.shields.io/github/license/elyby/chrly.svg?style=flat-square
|
||||
[ico-fossa]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Felyby%2Fchrly.svg?type=shield
|
||||
[ico-fossa-big]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Felyby%2Fchrly.svg?type=large
|
||||
|
||||
[link-go]: https://golang.org
|
||||
[link-build]: https://travis-ci.org/elyby/chrly
|
||||
[link-build]: https://github.com/elyby/chrly/actions
|
||||
[link-coverage]: https://codecov.io/gh/elyby/chrly
|
||||
[link-fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Felyby%2Fchrly
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/h2non/gock.v1"
|
||||
"github.com/h2non/gock"
|
||||
|
||||
testify "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "github.com/goava/di"
|
||||
. "github.com/defval/di"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build profiling
|
||||
// +build profiling
|
||||
|
||||
package cmd
|
||||
|
||||
@@ -3,6 +3,7 @@ package redis
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -10,23 +11,22 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mediocregopher/radix.v2/pool"
|
||||
"github.com/mediocregopher/radix.v2/redis"
|
||||
"github.com/mediocregopher/radix.v2/util"
|
||||
"github.com/mediocregopher/radix/v4"
|
||||
|
||||
"github.com/elyby/chrly/model"
|
||||
)
|
||||
|
||||
var now = time.Now
|
||||
|
||||
func New(addr string, poolSize int) (*Redis, error) {
|
||||
conn, err := pool.New("tcp", addr, poolSize)
|
||||
func New(ctx context.Context, addr string, poolSize int) (*Redis, error) {
|
||||
client, err := (radix.PoolConfig{Size: poolSize}).New(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Redis{
|
||||
pool: conn,
|
||||
client: client,
|
||||
context: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -34,27 +34,34 @@ const accountIdToUsernameKey = "hash:username-to-account-id" // TODO: this shoul
|
||||
const mojangUsernameToUuidKey = "hash:mojang-username-to-uuid"
|
||||
|
||||
type Redis struct {
|
||||
pool *pool.Pool
|
||||
client radix.Client
|
||||
context context.Context
|
||||
}
|
||||
|
||||
func (db *Redis) FindSkinByUsername(username string) (*model.Skin, error) {
|
||||
conn, err := db.pool.Get()
|
||||
var skin *model.Skin
|
||||
err := db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
var err error
|
||||
skin, err = findByUsername(ctx, conn, username)
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
return skin, err
|
||||
}
|
||||
|
||||
func findByUsername(ctx context.Context, conn radix.Conn, username string) (*model.Skin, error) {
|
||||
redisKey := buildUsernameKey(username)
|
||||
var encodedResult []byte
|
||||
err := conn.Do(ctx, radix.Cmd(&encodedResult, "GET", redisKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return findByUsername(username, conn)
|
||||
}
|
||||
|
||||
func findByUsername(username string, conn util.Cmder) (*model.Skin, error) {
|
||||
redisKey := buildUsernameKey(username)
|
||||
response := conn.Cmd("GET", redisKey)
|
||||
if response.IsType(redis.Nil) {
|
||||
if len(encodedResult) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
encodedResult, _ := response.Bytes()
|
||||
result, err := zlibDecode(encodedResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -66,62 +73,78 @@ func findByUsername(username string, conn util.Cmder) (*model.Skin, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Some old data causing issues in the production.
|
||||
// TODO: remove after investigation will be finished
|
||||
if skin.Uuid == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
skin.OldUsername = skin.Username
|
||||
|
||||
return skin, nil
|
||||
}
|
||||
|
||||
func (db *Redis) FindSkinByUserId(id int) (*model.Skin, error) {
|
||||
conn, err := db.pool.Get()
|
||||
var skin *model.Skin
|
||||
err := db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
var err error
|
||||
skin, err = findByUserId(ctx, conn, id)
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
return skin, err
|
||||
}
|
||||
|
||||
func findByUserId(ctx context.Context, conn radix.Conn, id int) (*model.Skin, error) {
|
||||
var username string
|
||||
err := conn.Do(ctx, radix.FlatCmd(&username, "HGET", accountIdToUsernameKey, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return findByUserId(id, conn)
|
||||
}
|
||||
|
||||
func findByUserId(id int, conn util.Cmder) (*model.Skin, error) {
|
||||
response := conn.Cmd("HGET", accountIdToUsernameKey, id)
|
||||
if response.IsType(redis.Nil) {
|
||||
if username == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
username, err := response.Str()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return findByUsername(username, conn)
|
||||
return findByUsername(ctx, conn, username)
|
||||
}
|
||||
|
||||
func (db *Redis) SaveSkin(skin *model.Skin) error {
|
||||
conn, err := db.pool.Get()
|
||||
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
return save(ctx, conn, skin)
|
||||
}))
|
||||
}
|
||||
|
||||
func save(ctx context.Context, conn radix.Conn, skin *model.Skin) error {
|
||||
err := conn.Do(ctx, radix.Cmd(nil, "MULTI"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return save(skin, conn)
|
||||
}
|
||||
|
||||
func save(skin *model.Skin, conn util.Cmder) error {
|
||||
conn.Cmd("MULTI")
|
||||
|
||||
// If user has changed username, then we must delete his old username record
|
||||
if skin.OldUsername != "" && skin.OldUsername != skin.Username {
|
||||
conn.Cmd("DEL", buildUsernameKey(skin.OldUsername))
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "DEL", buildUsernameKey(skin.OldUsername)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a new record or if the user has changed username, we set the value in the hash table
|
||||
if skin.OldUsername != "" || skin.OldUsername != skin.Username {
|
||||
conn.Cmd("HSET", accountIdToUsernameKey, skin.UserId, skin.Username)
|
||||
err = conn.Do(ctx, radix.FlatCmd(nil, "HSET", accountIdToUsernameKey, skin.UserId, skin.Username))
|
||||
}
|
||||
|
||||
str, _ := json.Marshal(skin)
|
||||
conn.Cmd("SET", buildUsernameKey(skin.Username), zlibEncode(str))
|
||||
err = conn.Do(ctx, radix.FlatCmd(nil, "SET", buildUsernameKey(skin.Username), zlibEncode(str)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Cmd("EXEC")
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "EXEC"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
skin.OldUsername = skin.Username
|
||||
|
||||
@@ -129,45 +152,45 @@ func save(skin *model.Skin, conn util.Cmder) error {
|
||||
}
|
||||
|
||||
func (db *Redis) RemoveSkinByUserId(id int) error {
|
||||
conn, err := db.pool.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return removeByUserId(id, conn)
|
||||
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
return removeByUserId(ctx, conn, id)
|
||||
}))
|
||||
}
|
||||
|
||||
func removeByUserId(id int, conn util.Cmder) error {
|
||||
record, err := findByUserId(id, conn)
|
||||
func removeByUserId(ctx context.Context, conn radix.Conn, id int) error {
|
||||
record, err := findByUserId(ctx, conn, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Cmd("MULTI")
|
||||
|
||||
conn.Cmd("HDEL", accountIdToUsernameKey, id)
|
||||
if record != nil {
|
||||
conn.Cmd("DEL", buildUsernameKey(record.Username))
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "MULTI"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Cmd("EXEC")
|
||||
err = conn.Do(ctx, radix.FlatCmd(nil, "HDEL", accountIdToUsernameKey, id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if record != nil {
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "DEL", buildUsernameKey(record.Username)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return conn.Do(ctx, radix.Cmd(nil, "EXEC"))
|
||||
}
|
||||
|
||||
func (db *Redis) RemoveSkinByUsername(username string) error {
|
||||
conn, err := db.pool.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return removeByUsername(username, conn)
|
||||
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
return removeByUsername(ctx, conn, username)
|
||||
}))
|
||||
}
|
||||
|
||||
func removeByUsername(username string, conn util.Cmder) error {
|
||||
record, err := findByUsername(username, conn)
|
||||
func removeByUsername(ctx context.Context, conn radix.Conn, username string) error {
|
||||
record, err := findByUsername(ctx, conn, username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -176,45 +199,68 @@ func removeByUsername(username string, conn util.Cmder) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
conn.Cmd("MULTI")
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "MULTI"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Cmd("DEL", buildUsernameKey(record.Username))
|
||||
conn.Cmd("HDEL", accountIdToUsernameKey, record.UserId)
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "DEL", buildUsernameKey(record.Username)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.Cmd("EXEC")
|
||||
err = conn.Do(ctx, radix.FlatCmd(nil, "HDEL", accountIdToUsernameKey, record.UserId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return conn.Do(ctx, radix.Cmd(nil, "EXEC"))
|
||||
}
|
||||
|
||||
func (db *Redis) GetUuid(username string) (string, bool, error) {
|
||||
conn, err := db.pool.Get()
|
||||
var uuid string
|
||||
var found bool
|
||||
err := db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
var err error
|
||||
uuid, found, err = findMojangUuidByUsername(ctx, conn, username)
|
||||
|
||||
return err
|
||||
}))
|
||||
|
||||
return uuid, found, err
|
||||
}
|
||||
|
||||
func findMojangUuidByUsername(ctx context.Context, conn radix.Conn, username string) (string, bool, error) {
|
||||
key := strings.ToLower(username)
|
||||
var result string
|
||||
err := conn.Do(ctx, radix.Cmd(&result, "HGET", mojangUsernameToUuidKey, key))
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return findMojangUuidByUsername(username, conn)
|
||||
}
|
||||
|
||||
func findMojangUuidByUsername(username string, conn util.Cmder) (string, bool, error) {
|
||||
key := strings.ToLower(username)
|
||||
response := conn.Cmd("HGET", mojangUsernameToUuidKey, key)
|
||||
if response.IsType(redis.Nil) {
|
||||
if result == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
data, _ := response.Str()
|
||||
parts := strings.Split(data, ":")
|
||||
parts := strings.Split(result, ":")
|
||||
// https://github.com/elyby/chrly/issues/28
|
||||
if len(parts) < 2 {
|
||||
conn.Cmd("HDEL", mojangUsernameToUuidKey, key)
|
||||
return "", false, fmt.Errorf("Got unexpected response from the mojangUsernameToUuid hash: \"%s\"", data)
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "HDEL", mojangUsernameToUuidKey, key))
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
return "", false, fmt.Errorf("got unexpected response from the mojangUsernameToUuid hash: \"%s\"", result)
|
||||
}
|
||||
|
||||
timestamp, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
storedAt := time.Unix(timestamp, 0)
|
||||
if storedAt.Add(time.Hour * 24 * 30).Before(now()) {
|
||||
conn.Cmd("HDEL", mojangUsernameToUuidKey, key)
|
||||
err = conn.Do(ctx, radix.Cmd(nil, "HDEL", mojangUsernameToUuidKey, key))
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
@@ -222,36 +268,23 @@ func findMojangUuidByUsername(username string, conn util.Cmder) (string, bool, e
|
||||
}
|
||||
|
||||
func (db *Redis) StoreUuid(username string, uuid string) error {
|
||||
conn, err := db.pool.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.pool.Put(conn)
|
||||
|
||||
return storeMojangUuid(username, uuid, conn)
|
||||
return db.client.Do(db.context, radix.WithConn("", func(ctx context.Context, conn radix.Conn) error {
|
||||
return storeMojangUuid(ctx, conn, username, uuid)
|
||||
}))
|
||||
}
|
||||
|
||||
func storeMojangUuid(username string, uuid string, conn util.Cmder) error {
|
||||
func storeMojangUuid(ctx context.Context, conn radix.Conn, username string, uuid string) error {
|
||||
value := uuid + ":" + strconv.FormatInt(now().Unix(), 10)
|
||||
res := conn.Cmd("HSET", mojangUsernameToUuidKey, strings.ToLower(username), value)
|
||||
if res.IsType(redis.Err) {
|
||||
return res.Err
|
||||
err := conn.Do(ctx, radix.Cmd(nil, "HSET", mojangUsernameToUuidKey, strings.ToLower(username), value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Redis) Ping() error {
|
||||
r := db.pool.Cmd("PING")
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Redis) Avail() int {
|
||||
return db.pool.Avail()
|
||||
return db.client.Do(db.context, radix.Cmd(nil, "PING"))
|
||||
}
|
||||
|
||||
func buildUsernameKey(username string) string {
|
||||
|
||||
@@ -1,33 +1,47 @@
|
||||
// +build redis
|
||||
//go:build redis
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mediocregopher/radix.v2/redis"
|
||||
"github.com/mediocregopher/radix/v4"
|
||||
assert "github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/elyby/chrly/model"
|
||||
)
|
||||
|
||||
const redisAddr = "localhost:6379"
|
||||
var redisAddr string
|
||||
|
||||
func init() {
|
||||
host := "localhost"
|
||||
port := 6379
|
||||
if os.Getenv("STORAGE_REDIS_HOST") != "" {
|
||||
host = os.Getenv("STORAGE_REDIS_HOST")
|
||||
}
|
||||
|
||||
if os.Getenv("STORAGE_REDIS_PORT") != "" {
|
||||
port, _ = strconv.Atoi(os.Getenv("STORAGE_REDIS_PORT"))
|
||||
}
|
||||
|
||||
redisAddr = fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Run("should connect", func(t *testing.T) {
|
||||
conn, err := New(redisAddr, 12)
|
||||
conn, err := New(context.Background(), redisAddr, 12)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, conn)
|
||||
internalPool := reflect.ValueOf(conn.pool).Elem().FieldByName("pool")
|
||||
assert.Equal(t, 12, internalPool.Cap())
|
||||
})
|
||||
|
||||
t.Run("should return error", func(t *testing.T) {
|
||||
conn, err := New("localhost:12345", 12) // Use localhost to avoid DNS resolution
|
||||
conn, err := New(context.Background(), "localhost:12345", 12) // Use localhost to avoid DNS resolution
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, conn)
|
||||
})
|
||||
@@ -38,21 +52,30 @@ type redisTestSuite struct {
|
||||
|
||||
Redis *Redis
|
||||
|
||||
cmd func(cmd string, args ...interface{}) *redis.Resp
|
||||
cmd func(cmd string, args ...interface{}) string
|
||||
}
|
||||
|
||||
func (suite *redisTestSuite) SetupSuite() {
|
||||
conn, err := New(redisAddr, 10)
|
||||
ctx := context.Background()
|
||||
conn, err := New(ctx, redisAddr, 10)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot establish connection to redis: %w", err))
|
||||
}
|
||||
|
||||
suite.Redis = conn
|
||||
suite.cmd = conn.pool.Cmd
|
||||
suite.cmd = func(cmd string, args ...interface{}) string {
|
||||
var result string
|
||||
err := suite.Redis.client.Do(ctx, radix.FlatCmd(&result, cmd, args...))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *redisTestSuite) SetupTest() {
|
||||
// Cleanup database before the each test
|
||||
// Cleanup database before each test
|
||||
suite.cmd("FLUSHALL")
|
||||
}
|
||||
|
||||
@@ -84,7 +107,7 @@ func TestRedis(t *testing.T) {
|
||||
* mojangSignature: "mock-mojang-signature"
|
||||
* }
|
||||
*/
|
||||
var skinRecord = []byte{
|
||||
var skinRecord = string([]byte{
|
||||
0x78, 0x9c, 0x5c, 0xce, 0x4b, 0x4a, 0x4, 0x41, 0xc, 0xc6, 0xf1, 0xbb, 0x7c, 0xeb, 0x1a, 0xdb, 0xd6, 0xb2,
|
||||
0x9c, 0xc9, 0xd, 0x5c, 0x88, 0x8b, 0xd1, 0xb5, 0x84, 0x4e, 0xa6, 0xa7, 0xec, 0x7a, 0xc, 0xf5, 0x0, 0x41,
|
||||
0xbc, 0xbb, 0xb4, 0xd2, 0xa, 0x2e, 0xf3, 0xe3, 0x9f, 0x90, 0xf, 0xf4, 0xaa, 0xe5, 0x41, 0x40, 0xa3, 0x41,
|
||||
@@ -95,7 +118,7 @@ var skinRecord = []byte{
|
||||
0xa0, 0x13, 0x87, 0xaa, 0x6, 0x31, 0xbf, 0x71, 0x9a, 0x9f, 0xf5, 0xbd, 0xf5, 0xa2, 0x15, 0x84, 0x98, 0xa7,
|
||||
0x65, 0xf7, 0xa3, 0xbb, 0xb6, 0xf1, 0xd6, 0x1d, 0xfd, 0x9c, 0x78, 0xa5, 0x7f, 0x61, 0xfd, 0x75, 0x83, 0xa7,
|
||||
0x20, 0x2f, 0x7f, 0xff, 0xe2, 0xf3, 0x2b, 0x0, 0x0, 0xff, 0xff, 0x6f, 0xdd, 0x51, 0x71,
|
||||
}
|
||||
})
|
||||
|
||||
func (suite *redisTestSuite) TestFindSkinByUsername() {
|
||||
suite.RunSubTest("exists record", func() {
|
||||
@@ -181,14 +204,11 @@ func (suite *redisTestSuite) TestSaveSkin() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
usernameResp := suite.cmd("GET", "username:mock")
|
||||
suite.Require().False(usernameResp.IsType(redis.Nil))
|
||||
bytes, _ := usernameResp.Bytes()
|
||||
suite.Require().Equal(skinRecord, bytes)
|
||||
suite.Require().NotEmpty(usernameResp)
|
||||
suite.Require().Equal(skinRecord, usernameResp)
|
||||
|
||||
idResp := suite.cmd("HGET", "hash:username-to-account-id", 1)
|
||||
suite.Require().False(usernameResp.IsType(redis.Nil))
|
||||
str, _ := idResp.Str()
|
||||
suite.Require().Equal("Mock", str)
|
||||
suite.Require().Equal("Mock", idResp)
|
||||
})
|
||||
|
||||
suite.RunSubTest("save exists record with changed username", func() {
|
||||
@@ -210,9 +230,8 @@ func (suite *redisTestSuite) TestSaveSkin() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
usernameResp := suite.cmd("GET", "username:newmock")
|
||||
suite.Require().False(usernameResp.IsType(redis.Nil))
|
||||
bytes, _ := usernameResp.Bytes()
|
||||
suite.Require().Equal([]byte{
|
||||
suite.Require().NotEmpty(usernameResp)
|
||||
suite.Require().Equal(string([]byte{
|
||||
0x78, 0x9c, 0x5c, 0x8e, 0xcb, 0x4e, 0xc3, 0x40, 0xc, 0x45, 0xff, 0xe5, 0xae, 0xa7, 0x84, 0x40, 0x18, 0x5a,
|
||||
0xff, 0x1, 0xb, 0x60, 0x51, 0x58, 0x23, 0x2b, 0x76, 0xd3, 0x21, 0xf3, 0xa8, 0xe6, 0x21, 0x90, 0x10, 0xff,
|
||||
0x8e, 0x52, 0x14, 0x90, 0xba, 0xf4, 0xd1, 0xf1, 0xd5, 0xf9, 0x42, 0x2b, 0x9a, 0x1f, 0x4, 0xd4, 0x1b, 0xb4,
|
||||
@@ -224,15 +243,14 @@ func (suite *redisTestSuite) TestSaveSkin() {
|
||||
0x42, 0x1a, 0xe7, 0xcd, 0x2f, 0xdd, 0xd4, 0x15, 0xaf, 0xde, 0xde, 0x4d, 0x91, 0x17, 0x74, 0x21, 0x96, 0x3f,
|
||||
0x6e, 0xf0, 0xec, 0xe5, 0xf5, 0x3f, 0xf9, 0xdc, 0xfb, 0xfd, 0x13, 0x0, 0x0, 0xff, 0xff, 0xca, 0xc3, 0x54,
|
||||
0x25,
|
||||
}, bytes)
|
||||
}), usernameResp)
|
||||
|
||||
oldUsernameResp := suite.cmd("GET", "username:mock")
|
||||
suite.Require().True(oldUsernameResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(oldUsernameResp)
|
||||
|
||||
idResp := suite.cmd("HGET", "hash:username-to-account-id", 1)
|
||||
suite.Require().False(usernameResp.IsType(redis.Nil))
|
||||
str, _ := idResp.Str()
|
||||
suite.Require().Equal("NewMock", str)
|
||||
suite.Require().NotEmpty(usernameResp)
|
||||
suite.Require().Equal("NewMock", idResp)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -245,10 +263,10 @@ func (suite *redisTestSuite) TestRemoveSkinByUserId() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
usernameResp := suite.cmd("GET", "username:mock")
|
||||
suite.Require().True(usernameResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(usernameResp)
|
||||
|
||||
idResp := suite.cmd("HGET", "hash:username-to-account-id", 1)
|
||||
suite.Require().True(idResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(idResp)
|
||||
})
|
||||
|
||||
suite.RunSubTest("exists only id", func() {
|
||||
@@ -258,7 +276,7 @@ func (suite *redisTestSuite) TestRemoveSkinByUserId() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
idResp := suite.cmd("HGET", "hash:username-to-account-id", 1)
|
||||
suite.Require().True(idResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(idResp)
|
||||
})
|
||||
|
||||
suite.RunSubTest("error when querying skin record", func() {
|
||||
@@ -279,10 +297,10 @@ func (suite *redisTestSuite) TestRemoveSkinByUsername() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
usernameResp := suite.cmd("GET", "username:mock")
|
||||
suite.Require().True(usernameResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(usernameResp)
|
||||
|
||||
idResp := suite.cmd("HGET", "hash:username-to-account-id", 1)
|
||||
suite.Require().True(idResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(idResp)
|
||||
})
|
||||
|
||||
suite.RunSubTest("exists only username", func() {
|
||||
@@ -292,7 +310,7 @@ func (suite *redisTestSuite) TestRemoveSkinByUsername() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
usernameResp := suite.cmd("GET", "username:mock")
|
||||
suite.Require().True(usernameResp.IsType(redis.Nil))
|
||||
suite.Require().Empty(usernameResp)
|
||||
})
|
||||
|
||||
suite.RunSubTest("no records", func() {
|
||||
@@ -355,7 +373,7 @@ func (suite *redisTestSuite) TestGetUuid() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
resp := suite.cmd("HGET", "hash:mojang-username-to-uuid", "mock")
|
||||
suite.Require().True(resp.IsType(redis.Nil), "should cleanup expired records")
|
||||
suite.Require().Empty(resp, "should cleanup expired records")
|
||||
})
|
||||
|
||||
suite.RunSubTest("exists, but corrupted record", func() {
|
||||
@@ -371,7 +389,7 @@ func (suite *redisTestSuite) TestGetUuid() {
|
||||
suite.Require().Error(err, "Got unexpected response from the mojangUsernameToUuid hash: \"corrupted value\"")
|
||||
|
||||
resp := suite.cmd("HGET", "hash:mojang-username-to-uuid", "mock")
|
||||
suite.Require().True(resp.IsType(redis.Nil), "should cleanup expired records")
|
||||
suite.Require().Empty(resp, "should cleanup expired records")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -385,9 +403,7 @@ func (suite *redisTestSuite) TestStoreUuid() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
resp := suite.cmd("HGET", "hash:mojang-username-to-uuid", "mock")
|
||||
suite.Require().False(resp.IsType(redis.Nil))
|
||||
str, _ := resp.Str()
|
||||
suite.Require().Equal(str, "d3ca513eb3e14946b58047f2bd3530fd:1587435016")
|
||||
suite.Require().Equal(resp, "d3ca513eb3e14946b58047f2bd3530fd:1587435016")
|
||||
})
|
||||
|
||||
suite.RunSubTest("store empty uuid", func() {
|
||||
@@ -399,9 +415,7 @@ func (suite *redisTestSuite) TestStoreUuid() {
|
||||
suite.Require().Nil(err)
|
||||
|
||||
resp := suite.cmd("HGET", "hash:mojang-username-to-uuid", "mock")
|
||||
suite.Require().False(resp.IsType(redis.Nil))
|
||||
str, _ := resp.Str()
|
||||
suite.Require().Equal(str, ":1587435016")
|
||||
suite.Require().Equal(resp, ":1587435016")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -409,8 +423,3 @@ func (suite *redisTestSuite) TestPing() {
|
||||
err := suite.Redis.Ping()
|
||||
suite.Require().Nil(err)
|
||||
}
|
||||
|
||||
func (suite *redisTestSuite) TestAvail() {
|
||||
avail := suite.Redis.Avail()
|
||||
suite.Require().True(avail > 0)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"github.com/goava/di"
|
||||
"github.com/defval/di"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
||||
10
di/db.go
10
di/db.go
@@ -4,9 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/goava/di"
|
||||
"github.com/defval/di"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/elyby/chrly/db/fs"
|
||||
@@ -38,6 +37,7 @@ func newRedis(container *di.Container, config *viper.Viper) (*redis.Redis, error
|
||||
config.SetDefault("storage.redis.poolSize", 10)
|
||||
|
||||
conn, err := redis.New(
|
||||
context.Background(),
|
||||
fmt.Sprintf("%s:%d", config.GetString("storage.redis.host"), config.GetInt("storage.redis.port")),
|
||||
config.GetInt("storage.redis.poolSize"),
|
||||
)
|
||||
@@ -45,12 +45,6 @@ func newRedis(container *di.Container, config *viper.Viper) (*redis.Redis, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := container.Provide(func() es.ReporterFunc {
|
||||
return es.AvailableRedisPoolSizeReporter(conn, time.Second, context.Background())
|
||||
}, di.As(new(es.Reporter))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := container.Provide(func() *namedHealthChecker {
|
||||
return &namedHealthChecker{
|
||||
Name: "redis",
|
||||
|
||||
2
di/di.go
2
di/di.go
@@ -1,6 +1,6 @@
|
||||
package di
|
||||
|
||||
import "github.com/goava/di"
|
||||
import "github.com/defval/di"
|
||||
|
||||
func New() (*di.Container, error) {
|
||||
container, err := di.New(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"github.com/goava/di"
|
||||
"github.com/defval/di"
|
||||
"github.com/mono83/slf"
|
||||
|
||||
d "github.com/elyby/chrly/dispatcher"
|
||||
@@ -30,7 +30,7 @@ func enableEventsHandlers(
|
||||
logger slf.Logger,
|
||||
statsReporter slf.StatsReporter,
|
||||
) {
|
||||
// TODO: use idea from https://github.com/goava/di/issues/10#issuecomment-615869852
|
||||
// TODO: use idea from https://github.com/defval/di/issues/10#issuecomment-615869852
|
||||
(&eventsubscribers.Logger{Logger: logger}).ConfigureWithDispatcher(dispatcher)
|
||||
(&eventsubscribers.StatsReporter{StatsReporter: statsReporter}).ConfigureWithDispatcher(dispatcher)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/etherlabsio/healthcheck"
|
||||
"github.com/goava/di"
|
||||
"github.com/defval/di"
|
||||
"github.com/etherlabsio/healthcheck/v2"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@@ -75,14 +76,14 @@ func newHandlerFactory(
|
||||
}
|
||||
|
||||
err := container.Invoke(enableReporters)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, di.ErrTypeNotExists) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Resolve health checkers last, because all the services required by the application
|
||||
// must first be initialized and each of them can publish its own checkers
|
||||
var healthCheckers []*namedHealthChecker
|
||||
if container.Has(&healthCheckers) {
|
||||
if has, _ := container.Has(&healthCheckers); has {
|
||||
if err := container.Resolve(&healthCheckers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -105,19 +106,24 @@ func newSkinsystemHandler(
|
||||
capesRepository CapesRepository,
|
||||
mojangTexturesProvider MojangTexturesProvider,
|
||||
texturesSigner TexturesSigner,
|
||||
) *mux.Router {
|
||||
) (*mux.Router, error) {
|
||||
config.SetDefault("textures.extra_param_name", "chrly")
|
||||
config.SetDefault("textures.extra_param_value", "how do you tame a horse in Minecraft?")
|
||||
|
||||
return (&Skinsystem{
|
||||
Emitter: emitter,
|
||||
SkinsRepo: skinsRepository,
|
||||
CapesRepo: capesRepository,
|
||||
MojangTexturesProvider: mojangTexturesProvider,
|
||||
TexturesSigner: texturesSigner,
|
||||
TexturesExtraParamName: config.GetString("textures.extra_param_name"),
|
||||
TexturesExtraParamValue: config.GetString("textures.extra_param_value"),
|
||||
}).Handler()
|
||||
app, err := NewSkinsystem(
|
||||
emitter,
|
||||
skinsRepository,
|
||||
capesRepository,
|
||||
mojangTexturesProvider,
|
||||
texturesSigner,
|
||||
config.GetString("textures.extra_param_name"),
|
||||
config.GetString("textures.extra_param_value"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return app.Handler(), nil
|
||||
}
|
||||
|
||||
func newApiHandler(skinsRepository SkinsRepository) *mux.Router {
|
||||
|
||||
@@ -3,8 +3,8 @@ package di
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/defval/di"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/goava/di"
|
||||
"github.com/mono83/slf"
|
||||
"github.com/mono83/slf/rays"
|
||||
"github.com/mono83/slf/recievers/sentry"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/goava/di"
|
||||
"github.com/defval/di"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/elyby/chrly/api/mojang"
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/defval/di"
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/goava/di"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
. "github.com/elyby/chrly/http"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
. "github.com/elyby/chrly/signer"
|
||||
"strings"
|
||||
|
||||
"github.com/goava/di"
|
||||
"github.com/defval/di"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/etherlabsio/healthcheck"
|
||||
"github.com/etherlabsio/healthcheck/v2"
|
||||
|
||||
"github.com/elyby/chrly/api/mojang"
|
||||
)
|
||||
|
||||
@@ -43,7 +43,9 @@ func TestDatabaseChecker(t *testing.T) {
|
||||
waitChan := make(chan time.Time, 1)
|
||||
p.On("Ping").WaitUntil(waitChan).Return(nil)
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 0)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 0)
|
||||
defer cancel()
|
||||
|
||||
checker := DatabaseChecker(p)
|
||||
assert.Errorf(t, checker(ctx), "check timeout")
|
||||
close(waitChan)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eventsubscribers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -30,7 +29,7 @@ func (f ReporterFunc) Enable(reporter slf.StatsReporter) {
|
||||
f(reporter)
|
||||
}
|
||||
|
||||
// TODO: rework all reporters in the same style as AvailableRedisPoolSizeReporter
|
||||
// TODO: rework all reporters in the same style as it was there: https://github.com/elyby/chrly/blob/1543e98b/di/db.go#L48-L52
|
||||
func (s *StatsReporter) ConfigureWithDispatcher(d Subscriber) {
|
||||
s.timersMap = make(map[string]time.Time)
|
||||
|
||||
@@ -133,6 +132,8 @@ func (s *StatsReporter) handleBeforeRequest(req *http.Request) {
|
||||
key = "signed_textures.request"
|
||||
} else if strings.HasPrefix(p, "/textures/") {
|
||||
key = "textures.request"
|
||||
} else if strings.HasPrefix(p, "/profile/") {
|
||||
key = "profiles.request"
|
||||
} else if m == http.MethodPost && p == "/api/skins" {
|
||||
key = "api.skins.post.request"
|
||||
} else if m == http.MethodDelete && strings.HasPrefix(p, "/api/skins/") {
|
||||
@@ -187,24 +188,3 @@ func (s *StatsReporter) finalizeTimeRecording(timeKey string, statName string) {
|
||||
|
||||
s.RecordTimer(statName, time.Since(startedAt))
|
||||
}
|
||||
|
||||
type RedisPoolCheckable interface {
|
||||
Avail() int
|
||||
}
|
||||
|
||||
func AvailableRedisPoolSizeReporter(pool RedisPoolCheckable, d time.Duration, stop context.Context) ReporterFunc {
|
||||
return func(reporter slf.StatsReporter) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(d)
|
||||
for {
|
||||
select {
|
||||
case <-stop.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
reporter.UpdateGauge("redis.pool.available", int64(pool.Avail()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eventsubscribers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -99,6 +98,14 @@ var statsReporterTestCases = []*StatsReporterTestCase{
|
||||
{"IncCounter", "signed_textures.request", int64(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Events: [][]interface{}{
|
||||
{"skinsystem:before_request", httptest.NewRequest("GET", "http://localhost/profile/username", nil)},
|
||||
},
|
||||
ExpectedCalls: [][]interface{}{
|
||||
{"IncCounter", "profiles.request", int64(1)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Events: [][]interface{}{
|
||||
{"skinsystem:before_request", httptest.NewRequest("POST", "http://localhost/api/skins", nil)},
|
||||
@@ -393,30 +400,3 @@ func TestStatsReporter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type redisPoolCheckableMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (r *redisPoolCheckableMock) Avail() int {
|
||||
return r.Called().Int(0)
|
||||
}
|
||||
|
||||
func TestAvailableRedisPoolSizeReporter(t *testing.T) {
|
||||
poolMock := &redisPoolCheckableMock{}
|
||||
poolMock.On("Avail").Return(5).Times(3)
|
||||
reporterMock := &StatsReporterMock{}
|
||||
reporterMock.On("UpdateGauge", "redis.pool.available", int64(5)).Times(3)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
creator := AvailableRedisPoolSizeReporter(poolMock, 10*time.Millisecond, ctx)
|
||||
creator(reporterMock)
|
||||
|
||||
time.Sleep(35 * time.Millisecond)
|
||||
|
||||
cancel()
|
||||
|
||||
poolMock.AssertExpectations(t)
|
||||
reporterMock.AssertExpectations(t)
|
||||
}
|
||||
|
||||
55
go.mod
Normal file
55
go.mod
Normal file
@@ -0,0 +1,55 @@
|
||||
module github.com/elyby/chrly
|
||||
|
||||
go 1.21
|
||||
|
||||
replace github.com/asaskevich/EventBus v0.0.0-20200330115301-33b3bc6a7ddc => github.com/erickskrauch/EventBus v0.0.0-20200330115301-33b3bc6a7ddc
|
||||
|
||||
// Main dependencies
|
||||
require (
|
||||
github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2
|
||||
github.com/asaskevich/EventBus v0.0.0-20200330115301-33b3bc6a7ddc
|
||||
github.com/defval/di v1.12.0
|
||||
github.com/etherlabsio/healthcheck/v2 v2.0.0
|
||||
github.com/getsentry/raven-go v0.2.1-0.20190419175539-919484f041ea
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/mediocregopher/radix/v4 v4.1.4
|
||||
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.18.1
|
||||
github.com/thedevsaddam/govalidator v1.9.10
|
||||
)
|
||||
|
||||
// Dev dependencies
|
||||
require (
|
||||
github.com/h2non/gock v1.2.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mono83/udpwriter v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tilinna/clock v1.0.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
106
go.sum
Normal file
106
go.sum
Normal file
@@ -0,0 +1,106 @@
|
||||
github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 h1:koK7z0nSsRiRiBWwa+E714Puh+DO+ZRdIyAXiXzL+lg=
|
||||
github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/defval/di v1.12.0 h1:xXm7BMX2+Nr0Yyu55DeJl/rmfCA7CQX89f4AGE0zA6U=
|
||||
github.com/defval/di v1.12.0/go.mod h1:PhVbOxQOvU7oawTOJXXTvqOJp1Dvsjs5PuzMw9gGl0I=
|
||||
github.com/erickskrauch/EventBus v0.0.0-20200330115301-33b3bc6a7ddc h1:kz3f5uMA1LxfRvJjZmMYG7Zu2rddTfJy6QZofz2YoGQ=
|
||||
github.com/erickskrauch/EventBus v0.0.0-20200330115301-33b3bc6a7ddc/go.mod h1:RHSo3YFV/SbOGyFR36RKWaXPy3g9nKAmn6ebNLpbco4=
|
||||
github.com/etherlabsio/healthcheck/v2 v2.0.0 h1:oKq8cbpwM/yNGPXf2Sff6MIjVUjx/pGYFydWzeK2MpA=
|
||||
github.com/etherlabsio/healthcheck/v2 v2.0.0/go.mod h1:huNVOjKzu6FI1eaO1CGD3ZjhrmPWf5Obu/pzpI6/wog=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/getsentry/raven-go v0.2.1-0.20190419175539-919484f041ea h1:t6e33/eet/VyiHHHKs0cBytUISUWQ/hmQwOlqtFoGEo=
|
||||
github.com/getsentry/raven-go v0.2.1-0.20190419175539-919484f041ea/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mediocregopher/radix/v4 v4.1.4 h1:Uze6DEbEAvL+VHXUEu/EDBTkUk5CLct5h3nVSGpc6Ts=
|
||||
github.com/mediocregopher/radix/v4 v4.1.4/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db h1:tlz4fTklh5mttoq5M+0yEc5Lap8W/02A2HCXCJn5iz0=
|
||||
github.com/mono83/slf v0.0.0-20170919161409-79153e9636db/go.mod h1:MfF+zNMZz+5IGY9h8jpFaGLyGoJ2ZPri2FmUVftBoUU=
|
||||
github.com/mono83/udpwriter v1.0.2 h1:JiQ/N646oZoJA1G0FOMvn2teMt6SdL1KwNH2mszOlQs=
|
||||
github.com/mono83/udpwriter v1.0.2/go.mod h1:mTDiyLtA0tXoxckkV9T4NUkJTgSQIuO8pAUKx/dSRkQ=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.1 h1:rmuU42rScKWlhhJDyXZRKJQHXFX02chSVW1IvkPGiVM=
|
||||
github.com/spf13/viper v1.18.1/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/thedevsaddam/govalidator v1.9.10 h1:m3dLRbSZ5Hts3VUWYe+vxLMG+FdyQuWOjzTeQRiMCvU=
|
||||
github.com/thedevsaddam/govalidator v1.9.10/go.mod h1:Ilx8u7cg5g3LXbSS943cx5kczyNuUn7LH/cK5MYuE90=
|
||||
github.com/tilinna/clock v1.0.2 h1:6BO2tyAC9JbPExKH/z9zl44FLu1lImh3nDNKA0kgrkI=
|
||||
github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
51
http/api.go
51
http/api.go
@@ -13,20 +13,12 @@ import (
|
||||
"github.com/elyby/chrly/model"
|
||||
)
|
||||
|
||||
//noinspection GoSnakeCaseUsage
|
||||
// noinspection GoSnakeCaseUsage
|
||||
const UUID_ANY = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
|
||||
var regexUuidAny = regexp.MustCompile(UUID_ANY)
|
||||
|
||||
func init() {
|
||||
govalidator.AddCustomRule("skinUploadingNotAvailable", func(field string, rule string, message string, value interface{}) error {
|
||||
if message == "" {
|
||||
message = "Skin uploading is temporary unavailable"
|
||||
}
|
||||
|
||||
return errors.New(message)
|
||||
})
|
||||
|
||||
// Add ability to validate any possible uuid form
|
||||
govalidator.AddCustomRule("uuid_any", func(field string, rule string, message string, value interface{}) error {
|
||||
str := value.(string)
|
||||
@@ -163,50 +155,41 @@ func (ctx *Api) findIdentityOrCleanup(identityId int, username string) (*model.S
|
||||
}
|
||||
|
||||
func validatePostSkinRequest(request *http.Request) map[string][]string {
|
||||
const maxMultipartMemory int64 = 32 << 20
|
||||
const oneOfSkinOrUrlMessage = "One of url or skin should be provided, but not both"
|
||||
|
||||
_ = request.ParseMultipartForm(maxMultipartMemory)
|
||||
_ = request.ParseForm()
|
||||
|
||||
validationRules := govalidator.MapData{
|
||||
"identityId": {"required", "numeric", "min:1"},
|
||||
"username": {"required"},
|
||||
"uuid": {"required", "uuid_any"},
|
||||
"skinId": {"required", "numeric", "min:1"},
|
||||
"url": {"url"},
|
||||
"file:skin": {"ext:png", "size:24576", "mime:image/png"},
|
||||
"is1_8": {"bool"},
|
||||
"isSlim": {"bool"},
|
||||
"identityId": {"required", "numeric", "min:1"},
|
||||
"username": {"required"},
|
||||
"uuid": {"required", "uuid_any"},
|
||||
"skinId": {"required", "numeric"},
|
||||
"url": {},
|
||||
"is1_8": {"bool"},
|
||||
"isSlim": {"bool"},
|
||||
"mojangTextures": {},
|
||||
"mojangSignature": {},
|
||||
}
|
||||
|
||||
shouldAppendSkinRequiredError := false
|
||||
url := request.Form.Get("url")
|
||||
_, _, skinErr := request.FormFile("skin")
|
||||
if (url != "" && skinErr == nil) || (url == "" && skinErr != nil) {
|
||||
shouldAppendSkinRequiredError = true
|
||||
} else if skinErr == nil {
|
||||
validationRules["file:skin"] = append(validationRules["file:skin"], "skinUploadingNotAvailable")
|
||||
} else if url != "" {
|
||||
if url == "" {
|
||||
validationRules["skinId"] = append(validationRules["skinId"], "numeric_between:0,0")
|
||||
} else {
|
||||
validationRules["url"] = append(validationRules["url"], "url")
|
||||
validationRules["skinId"] = append(validationRules["skinId"], "numeric_between:1,")
|
||||
validationRules["is1_8"] = append(validationRules["is1_8"], "required")
|
||||
validationRules["isSlim"] = append(validationRules["isSlim"], "required")
|
||||
}
|
||||
|
||||
mojangTextures := request.Form.Get("mojangTextures")
|
||||
if mojangTextures != "" {
|
||||
validationRules["mojangSignature"] = []string{"required"}
|
||||
validationRules["mojangSignature"] = append(validationRules["mojangSignature"], "required")
|
||||
}
|
||||
|
||||
validator := govalidator.New(govalidator.Options{
|
||||
Request: request,
|
||||
Rules: validationRules,
|
||||
RequiredDefault: false,
|
||||
FormSize: maxMultipartMemory,
|
||||
})
|
||||
validationResults := validator.Validate()
|
||||
if shouldAppendSkinRequiredError {
|
||||
validationResults["url"] = append(validationResults["url"], oneOfSkinOrUrlMessage)
|
||||
validationResults["skin"] = append(validationResults["skin"], oneOfSkinOrUrlMessage)
|
||||
}
|
||||
|
||||
if len(validationResults) != 0 {
|
||||
return validationResults
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -136,6 +135,41 @@ var postSkinTestsCases = []*postSkinTestCase{
|
||||
suite.Empty(body)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Update exists identity by changing textures data to empty",
|
||||
Form: bytes.NewBufferString(url.Values{
|
||||
"identityId": {"1"},
|
||||
"username": {"mock_username"},
|
||||
"uuid": {"0f657aa8-bfbe-415d-b700-5750090d3af3"},
|
||||
"skinId": {"0"},
|
||||
"is1_8": {"0"},
|
||||
"isSlim": {"0"},
|
||||
"url": {""},
|
||||
"mojangTextures": {""},
|
||||
"mojangSignature": {""},
|
||||
}.Encode()),
|
||||
BeforeTest: func(suite *apiTestSuite) {
|
||||
suite.SkinsRepository.On("FindSkinByUserId", 1).Return(createSkinModel("mock_username", false), nil)
|
||||
suite.SkinsRepository.On("SaveSkin", mock.MatchedBy(func(model *model.Skin) bool {
|
||||
suite.Equal(1, model.UserId)
|
||||
suite.Equal("mock_username", model.Username)
|
||||
suite.Equal("0f657aa8-bfbe-415d-b700-5750090d3af3", model.Uuid)
|
||||
suite.Equal(0, model.SkinId)
|
||||
suite.False(model.Is1_8)
|
||||
suite.False(model.IsSlim)
|
||||
suite.Equal("", model.Url)
|
||||
suite.Equal("", model.MojangTextures)
|
||||
suite.Equal("", model.MojangSignature)
|
||||
|
||||
return true
|
||||
})).Times(1).Return(nil)
|
||||
},
|
||||
AfterTest: func(suite *apiTestSuite, response *http.Response) {
|
||||
suite.Equal(201, response.StatusCode)
|
||||
body, _ := io.ReadAll(response.Body)
|
||||
suite.Equal("", string(body))
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Update exists identity by changing its identityId",
|
||||
Form: bytes.NewBufferString(url.Values{
|
||||
@@ -271,7 +305,7 @@ func (suite *apiTestSuite) TestPostSkin() {
|
||||
"skinId": [
|
||||
"The skinId field is required",
|
||||
"The skinId field must be numeric",
|
||||
"The skinId field must be minimum 1 char"
|
||||
"The skinId field must be numeric value between 0 and 0"
|
||||
],
|
||||
"username": [
|
||||
"The username field is required"
|
||||
@@ -280,54 +314,12 @@ func (suite *apiTestSuite) TestPostSkin() {
|
||||
"The uuid field is required",
|
||||
"The uuid field must contain valid UUID"
|
||||
],
|
||||
"url": [
|
||||
"One of url or skin should be provided, but not both"
|
||||
],
|
||||
"skin": [
|
||||
"One of url or skin should be provided, but not both"
|
||||
],
|
||||
"mojangSignature": [
|
||||
"The mojangSignature field is required"
|
||||
]
|
||||
}
|
||||
}`, string(body))
|
||||
})
|
||||
|
||||
suite.RunSubTest("Upload textures with skin as file", func() {
|
||||
inputBody := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(inputBody)
|
||||
|
||||
part, _ := writer.CreateFormFile("skin", "char.png")
|
||||
_, _ = part.Write(loadSkinFile())
|
||||
|
||||
_ = writer.WriteField("identityId", "1")
|
||||
_ = writer.WriteField("username", "mock_user")
|
||||
_ = writer.WriteField("uuid", "0f657aa8-bfbe-415d-b700-5750090d3af3")
|
||||
_ = writer.WriteField("skinId", "5")
|
||||
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("POST", "http://chrly/skins", inputBody)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.App.Handler().ServeHTTP(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
defer resp.Body.Close()
|
||||
suite.Equal(400, resp.StatusCode)
|
||||
responseBody, _ := ioutil.ReadAll(resp.Body)
|
||||
suite.JSONEq(`{
|
||||
"errors": {
|
||||
"skin": [
|
||||
"Skin uploading is temporary unavailable"
|
||||
]
|
||||
}
|
||||
}`, string(responseBody))
|
||||
})
|
||||
}
|
||||
|
||||
/**************************************
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestJwtAuth_Authenticate(t *testing.T) {
|
||||
emitter.On("Emit", "authentication:success")
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer " + jwt)
|
||||
req.Header.Add("Authorization", "Bearer "+jwt)
|
||||
jwt := &JwtAuth{Key: []byte("secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
@@ -99,7 +99,7 @@ func TestJwtAuth_Authenticate(t *testing.T) {
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer " + jwt)
|
||||
req.Header.Add("Authorization", "Bearer "+jwt)
|
||||
jwt := &JwtAuth{Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
@@ -116,7 +116,7 @@ func TestJwtAuth_Authenticate(t *testing.T) {
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest("POST", "http://localhost", nil)
|
||||
req.Header.Add("Authorization", "Bearer " + jwt)
|
||||
req.Header.Add("Authorization", "Bearer "+jwt)
|
||||
jwt := &JwtAuth{Key: []byte("this is another secret"), Emitter: emitter}
|
||||
|
||||
err := jwt.Authenticate(req)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"github.com/elyby/chrly/utils"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/elyby/chrly/api/mojang"
|
||||
"github.com/elyby/chrly/model"
|
||||
"github.com/elyby/chrly/utils"
|
||||
)
|
||||
|
||||
var timeNow = time.Now
|
||||
@@ -43,12 +44,39 @@ type TexturesSigner interface {
|
||||
|
||||
type Skinsystem struct {
|
||||
Emitter
|
||||
SkinsRepo SkinsRepository
|
||||
CapesRepo CapesRepository
|
||||
MojangTexturesProvider MojangTexturesProvider
|
||||
TexturesSigner TexturesSigner
|
||||
TexturesExtraParamName string
|
||||
TexturesExtraParamValue string
|
||||
SkinsRepo SkinsRepository
|
||||
CapesRepo CapesRepository
|
||||
MojangTexturesProvider MojangTexturesProvider
|
||||
TexturesSigner TexturesSigner
|
||||
TexturesExtraParamName string
|
||||
TexturesExtraParamValue string
|
||||
texturesExtraParamSignature string
|
||||
}
|
||||
|
||||
func NewSkinsystem(
|
||||
emitter Emitter,
|
||||
skinsRepo SkinsRepository,
|
||||
capesRepo CapesRepository,
|
||||
mojangTexturesProvider MojangTexturesProvider,
|
||||
texturesSigner TexturesSigner,
|
||||
texturesExtraParamName string,
|
||||
texturesExtraParamValue string,
|
||||
) (*Skinsystem, error) {
|
||||
texturesExtraParamSignature, err := texturesSigner.SignTextures(texturesExtraParamValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate signature for textures extra param: %w", err)
|
||||
}
|
||||
|
||||
return &Skinsystem{
|
||||
Emitter: emitter,
|
||||
SkinsRepo: skinsRepo,
|
||||
CapesRepo: capesRepo,
|
||||
MojangTexturesProvider: mojangTexturesProvider,
|
||||
TexturesSigner: texturesSigner,
|
||||
TexturesExtraParamName: texturesExtraParamName,
|
||||
TexturesExtraParamValue: texturesExtraParamValue,
|
||||
texturesExtraParamSignature: texturesExtraParamSignature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type profile struct {
|
||||
@@ -190,8 +218,15 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
|
||||
}
|
||||
|
||||
if profile == nil {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
forceResponseWithUuid := request.URL.Query().Get("onUnknownProfileRespondWithUuid")
|
||||
if forceResponseWithUuid == "" {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
profile = createEmptyProfile()
|
||||
profile.Id = formatUuid(forceResponseWithUuid)
|
||||
profile.Username = parseUsername(mux.Vars(request)["username"])
|
||||
}
|
||||
|
||||
texturesPropContent := &mojang.TexturesProp{
|
||||
@@ -208,14 +243,20 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
|
||||
Name: "textures",
|
||||
Value: texturesPropEncodedValue,
|
||||
}
|
||||
customProp := &mojang.Property{
|
||||
Name: ctx.TexturesExtraParamName,
|
||||
Value: ctx.TexturesExtraParamValue,
|
||||
}
|
||||
|
||||
if request.URL.Query().Get("unsigned") == "false" {
|
||||
signature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
|
||||
customProp.Signature = ctx.texturesExtraParamSignature
|
||||
|
||||
texturesSignature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
texturesProp.Signature = signature
|
||||
texturesProp.Signature = texturesSignature
|
||||
}
|
||||
|
||||
profileResponse := &mojang.SignedTexturesResponse{
|
||||
@@ -223,10 +264,7 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
|
||||
Name: profile.Username,
|
||||
Props: []*mojang.Property{
|
||||
texturesProp,
|
||||
{
|
||||
Name: ctx.TexturesExtraParamName,
|
||||
Value: ctx.TexturesExtraParamValue,
|
||||
},
|
||||
customProp,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -264,7 +302,8 @@ func (ctx *Skinsystem) signatureVerificationKeyHandler(response http.ResponseWri
|
||||
}
|
||||
|
||||
// TODO: in v5 should be extracted into some ProfileProvider interface,
|
||||
// which will encapsulate all logics, declared in this method
|
||||
//
|
||||
// which will encapsulate all logics, declared in this method
|
||||
func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile, error) {
|
||||
username := parseUsername(mux.Vars(request)["username"])
|
||||
|
||||
@@ -273,21 +312,14 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile := &profile{
|
||||
Id: "",
|
||||
Username: "",
|
||||
Textures: &mojang.TexturesResponse{}, // Field must be initialized to avoid "null" after json encoding
|
||||
CapeFile: nil,
|
||||
MojangTextures: "",
|
||||
MojangSignature: "",
|
||||
}
|
||||
profile := createEmptyProfile()
|
||||
|
||||
if skin != nil {
|
||||
profile.Id = strings.Replace(skin.Uuid, "-", "", -1)
|
||||
profile.Id = formatUuid(skin.Uuid)
|
||||
profile.Username = skin.Username
|
||||
}
|
||||
|
||||
if skin != nil && skin.SkinId != 0 {
|
||||
if skin != nil && skin.Url != "" {
|
||||
profile.Textures.Skin = &mojang.SkinTexturesResponse{
|
||||
Url: skin.Url,
|
||||
}
|
||||
@@ -349,6 +381,8 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
|
||||
profile.Id = mojangProfile.Id
|
||||
profile.Username = mojangProfile.Name
|
||||
}
|
||||
} else if profile.Id != "" {
|
||||
return profile, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -356,6 +390,16 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func createEmptyProfile() *profile {
|
||||
return &profile{
|
||||
Textures: &mojang.TexturesResponse{}, // Field must be initialized to avoid "null" after json encoding
|
||||
}
|
||||
}
|
||||
|
||||
func formatUuid(uuid string) string {
|
||||
return strings.Replace(uuid, "-", "", -1)
|
||||
}
|
||||
|
||||
func parseUsername(username string) string {
|
||||
return strings.TrimSuffix(username, ".png")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -141,15 +142,17 @@ func (suite *skinsystemTestSuite) SetupTest() {
|
||||
suite.TexturesSigner = &texturesSignerMock{}
|
||||
suite.Emitter = &emitterMock{}
|
||||
|
||||
suite.App = &Skinsystem{
|
||||
SkinsRepo: suite.SkinsRepository,
|
||||
CapesRepo: suite.CapesRepository,
|
||||
MojangTexturesProvider: suite.MojangTexturesProvider,
|
||||
TexturesSigner: suite.TexturesSigner,
|
||||
Emitter: suite.Emitter,
|
||||
TexturesExtraParamName: "texturesParamName",
|
||||
TexturesExtraParamValue: "texturesParamValue",
|
||||
}
|
||||
suite.TexturesSigner.On("SignTextures", "texturesParamValue").Times(1).Return("texturesParamSignature", nil)
|
||||
|
||||
suite.App, _ = NewSkinsystem(
|
||||
suite.Emitter,
|
||||
suite.SkinsRepository,
|
||||
suite.CapesRepository,
|
||||
suite.MojangTexturesProvider,
|
||||
suite.TexturesSigner,
|
||||
"texturesParamName",
|
||||
"texturesParamValue",
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *skinsystemTestSuite) TearDownTest() {
|
||||
@@ -742,11 +745,12 @@ func (suite *skinsystemTestSuite) TestSignedTextures() {
|
||||
***************************/
|
||||
|
||||
type profileTestCase struct {
|
||||
Name string
|
||||
Signed bool
|
||||
BeforeTest func(suite *skinsystemTestSuite)
|
||||
PanicErr string
|
||||
AfterTest func(suite *skinsystemTestSuite, response *http.Response)
|
||||
Name string
|
||||
Signed bool
|
||||
ForceResponse string
|
||||
BeforeTest func(suite *skinsystemTestSuite)
|
||||
PanicErr string
|
||||
AfterTest func(suite *skinsystemTestSuite, response *http.Response)
|
||||
}
|
||||
|
||||
var profileTestsCases = []*profileTestCase{
|
||||
@@ -799,6 +803,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -828,6 +833,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -857,6 +863,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -890,6 +897,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -923,6 +931,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -952,6 +961,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -981,6 +991,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -1010,6 +1021,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -1028,6 +1040,37 @@ var profileTestsCases = []*profileTestCase{
|
||||
suite.Equal("", string(body))
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Username not exists and Mojang profile unavailable, but there is a forceResponse param",
|
||||
ForceResponse: "a12e41a4-e8e5-4503-987e-0adacf72ab93",
|
||||
Signed: true,
|
||||
BeforeTest: func(suite *skinsystemTestSuite) {
|
||||
suite.SkinsRepository.On("FindSkinByUsername", "mock_username").Return(nil, nil)
|
||||
suite.MojangTexturesProvider.On("GetForUsername", "mock_username").Once().Return(nil, nil)
|
||||
suite.TexturesSigner.On("SignTextures", mock.Anything).Return("chrly signature", nil)
|
||||
},
|
||||
AfterTest: func(suite *skinsystemTestSuite, response *http.Response) {
|
||||
suite.Equal(200, response.StatusCode)
|
||||
suite.Equal("application/json", response.Header.Get("Content-Type"))
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
suite.JSONEq(`{
|
||||
"id": "a12e41a4e8e54503987e0adacf72ab93",
|
||||
"name": "mock_username",
|
||||
"properties": [
|
||||
{
|
||||
"name": "textures",
|
||||
"signature": "chrly signature",
|
||||
"value": "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6ImExMmU0MWE0ZThlNTQ1MDM5ODdlMGFkYWNmNzJhYjkzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnt9fQ=="
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
}`, string(body))
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Username not exists and Mojang textures proxy returned an error",
|
||||
BeforeTest: func(suite *skinsystemTestSuite) {
|
||||
@@ -1060,12 +1103,18 @@ func (suite *skinsystemTestSuite) TestProfile() {
|
||||
suite.RunSubTest(testCase.Name, func() {
|
||||
testCase.BeforeTest(suite)
|
||||
|
||||
url := "http://chrly/profile/mock_username"
|
||||
u, _ := url.Parse("http://chrly/profile/mock_username")
|
||||
q := make(url.Values)
|
||||
if testCase.Signed {
|
||||
url += "?unsigned=false"
|
||||
q.Set("unsigned", "false")
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", url, nil)
|
||||
if testCase.ForceResponse != "" {
|
||||
q.Set("onUnknownProfileRespondWithUuid", testCase.ForceResponse)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
req := httptest.NewRequest("GET", u.String(), nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if testCase.PanicErr != "" {
|
||||
|
||||
@@ -4,7 +4,7 @@ type Skin struct {
|
||||
UserId int `json:"userId"`
|
||||
Uuid string `json:"uuid"`
|
||||
Username string `json:"username"`
|
||||
SkinId int `json:"skinId"`
|
||||
SkinId int `json:"skinId"` // deprecated
|
||||
Url string `json:"url"`
|
||||
Is1_8 bool `json:"is1_8"`
|
||||
IsSlim bool `json:"isSlim"`
|
||||
|
||||
@@ -412,7 +412,8 @@ func TestFullBusStrategy(t *testing.T) {
|
||||
// Don't assert iteration.Queue length since it might be unstable
|
||||
// Don't call iteration.Done()
|
||||
case <-time.After(d):
|
||||
t.Fatalf("iteration should be provided as soon as the bus is full")
|
||||
t.Errorf("iteration should be provided as soon as the bus is full")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ func (c *broadcaster) BroadcastAndRemove(username string, result *broadcastResul
|
||||
delete(c.listeners, username)
|
||||
}
|
||||
|
||||
// https://help.mojang.com/customer/portal/articles/928638
|
||||
var allowedUsernamesRegex = regexp.MustCompile(`^[\w_]{3,16}$`)
|
||||
// https://help.minecraft.net/hc/en-us/articles/4408950195341#h_01GE5JX1Z0CZ833A7S54Y195KV
|
||||
var allowedUsernamesRegex = regexp.MustCompile(`(?i)^[0-9a-z_]{3,16}$`)
|
||||
|
||||
type UUIDsProvider interface {
|
||||
GetUuid(username string) (*mojang.ProfileInfo, error)
|
||||
|
||||
@@ -19,7 +19,7 @@ var HttpClient = &http.Client{
|
||||
|
||||
type RemoteApiUuidsProvider struct {
|
||||
Emitter
|
||||
Url URL
|
||||
Url URL
|
||||
}
|
||||
|
||||
func (ctx *RemoteApiUuidsProvider) GetUuid(username string) (*mojang.ProfileInfo, error) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
. "net/url"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/h2non/gock.v1"
|
||||
"github.com/h2non/gock"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
Reference in New Issue
Block a user