9 Commits
Author SHA1 Message Date
Sansan 2c3e97190d Prefer UUIDs in the API (Phase 3 of the UUID migration)
CI / docker (push) Successful in 46s
CI / integration (push) Successful in 23s
CI / test (push) Successful in 30s
CI / publish (push) Skipped
- New /api/v1 endpoints: GET /soundtracks, /soundtracks/{uuid},
  /songs/{uuid} and /songs/{uuid}/file (streams the song, Range support,
  locked during sync)
- UUIDs added to every response with soundtracks or songs: queue info and
  list (SoundtrackUuid, SongUuid) and statistics (soundtrack_uuid,
  song_uuid). Existing fields stay for backward compatibility
- Play counts and broken-song removal use song UUIDs instead of
  soundtrack ID + song name, so songs with the same name are counted
  separately
- Statistics return an empty song list for soundtracks without songs
  instead of a single empty entry
- The legacy .id file cleanup stays until Phase 4 (#31)

Closes #29
2026-10-06 15:54:41 +02:00
Sansan 8c974a49cc Merge workflows into one CI pipeline
CI / test (push) Successful in 31s
CI / integration (push) Successful in 39s
CI / publish (push) Skipped
CI / docker (push) Successful in 54s
- Unit tests, integration tests and the Docker build check run in
  parallel on branches and pull requests
- Integration tests run on every push instead of only manually
- Branch builds only build amd64 and check the arm64 cross-compile in the
  test job; both platforms are built and pushed only for version tags,
  after all tests pass
- Docker layers cached through the runner cache, Go module and build
  caches mounted in the Dockerfile
- A newer push to the same branch cancels the running pipeline
2026-10-06 15:35:32 +02:00
Sansan 9db61956d3 Publish Docker image when a version tag is pushed
Build / test (push) Successful in 8s
Build / docker (push) Successful in 49s
Releases are triggered by tags like v5.0.0 instead of version branches.
2026-10-06 15:24:06 +02:00
Sansan bd250ecaee Run tests in CI and build multi-arch Docker images
Build / test (push) Successful in 38s
Build / docker (push) Successful in 2m2s
- Build (main, develop): vet and unit tests, then a linux/amd64 and
  linux/arm64 image build without pushing
- Publish (version branches): same tests, then build and push both
  platforms
- Integration tests (manual): run the testcontainers suite
2026-10-06 15:22:13 +02:00
Sansan 59dfff38bc Clean up build tooling and Dockerfile
- Dockerfile cross-compiles Go for the target platform (amd64/arm64
  without emulation), uses templ pinned in go.mod, runs as uid 1000 on
  alpine and lets FRONTEND_IMAGE pin the frontend version
- justfile: tools via go tool, `test` (unit) and `test-integration`
  (uses the active docker context), no hardcoded paths, no tailwind or
  frontend recipes
- Removed app.yaml, init.sh, tailwind.config.js and the tracked .idea
  folder
2026-10-06 15:21:46 +02:00
Sansan 7b144ae419 Rewrite tests to run without a database
- Unit tests use an in-memory fake database (internal/testutil/fakedb),
  temp folders and httptest; `go test ./...` needs no Docker or env vars
- Integration tests (build tag "integration") start PostgreSQL with
  testcontainers: migrations step by step, SQL dumps (skipped if
  missing) and a full sync against a real database
- Removed tests that only checked copies of the logic or had empty
  bodies, the broken root integration_test.go, compose.test.yaml and
  .env.test with hardcoded paths
- testMusic MP3s replaced with tiny placeholders (sync never decodes
  audio), 194 MB -> 168 KB
2026-10-06 15:20:31 +02:00
Sansan 090e794460 Restructure backend into services and fix security, crash and sync issues
Security
- Serve character images through os.Root so names like ../ can't escape
  the characters folder
- Sync start and /sync/reset are POST only (legacy and /api/v1)
- /api/v1/token/cleanup requires a token, password compared in constant
  time, tokens no longer logged
- Echo's rate limiter replaces the custom one; TRUSTED_PROXIES enables
  X-Forwarded-For for reverse proxies

Crashes and races
- Sync state lives in SyncService with atomics and a mutex instead of
  unsynchronised globals (fixes concurrent map writes during full sync)
- Only one sync can run; /api/v1 sync and music routes are locked during
  sync like the legacy ones
- No log.Fatal outside startup: errors become 4xx/5xx responses
- Search uses an in-order character match instead of unescaped regexes
- Empty queue / no songs return 404 instead of panicking or looping

Behaviour
- Sync result is computed once and stays the same between polls
- Swagger and /openapi serve the embedded spec
- Shutdown stops HTTP before closing the database
- Expired tokens are deleted with a context that outlives the request
- Files are served with Range support and correct content types; audio
  has no write deadline

Performance
- Migration 000006 adds soundtrack.fingerprint (names, sizes, mtimes) so
  unchanged folders are skipped without reading any files
- Songs are reconciled per soundtrack with one query plus needed writes;
  renames keep play counts
- Moved MUSIC_PATH updates soundtrack and song paths
- .id cleanup only runs for changed soundtracks or a full sync
- Classic random uses one SQL query; Gitea release lookups are cached
  with a timeout

Structure
- internal/config reads all environment variables once
- sqlc emits a Querier interface; backend globals become services
  injected into handlers
- One route registration shared by legacy and /api/v1
- Removed dead code (dbHelper.go, unused middleware, duplicate Gitea
  functions, embedded Swagger UI copy) and lib/pq
- templ and swag pinned as go tools
2026-10-06 15:20:14 +02:00
Sansan 75b6f0c1cf Remove frontend source from cmd
The frontend is built separately and copied into the Docker image from
the musicfrontend image.
2026-10-06 15:19:49 +02:00
Sansan 2406fe9eb6 Ignore database dumps
Build / build (push) Failing after 34s
2026-10-05 18:49:15 +02:00
200 changed files with 6830 additions and 21848 deletions

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ tmp_dir = "tmp"
bin = "./tmp/main" bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd" cmd = "go build -o ./tmp/main ./cmd"
delay = 1000 delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "frontend", "swagger", "cmd/frontend", "cmd/swagger"] exclude_dir = ["assets", "tmp", "vendor", "testdata", "swagger", "cmd/swagger"]
exclude_file = [] exclude_file = []
exclude_regex = ["_test.go"] exclude_regex = ["_test.go"]
exclude_unchanged = false exclude_unchanged = false
+3 -9
View File
@@ -14,18 +14,12 @@ compose.yaml
*.pprof *.pprof
main main
conf.yaml conf.yaml
output.css
tailwindcss
.env .env
# Node.js (frontend build artifacts) # Test files and fixtures
node_modules
package.json
package-lock.json
# Test files
*_test.go *_test.go
integration_test.go internal/testutil
*_dump.sql
# OS # OS
.DS_Store .DS_Store
-19
View File
@@ -1,19 +0,0 @@
# Test Database Configuration
DB_HOST=localhost
DB_PORT=5433
DB_USERNAME=testuser
DB_PASSWORD=testpass
DB_NAME=music_server_test
# Test Paths
MUSIC_PATH=/Users/sebastian/projects/MusicServer/testMusic
CHARACTERS_PATH=/Users/sebastian/projects/MusicServer/testCharacters
# Server Configuration
PORT=8081
LOG_LEVEL=debug
LOG_JSON=false
# Session Authentication (for testing)
# Set to disable session auth during development
DISABLE_SESSION_AUTH=true
+107
View File
@@ -0,0 +1,107 @@
name: CI
# Branches and pull requests: unit tests, integration tests and a check that the image builds (amd64 only).
# Version tags (v1.2.3): the same tests, then build amd64 + arm64 and push the image.
on:
push:
branches:
- main
- develop
tags:
- "v*.*.*"
pull_request:
workflow_dispatch:
# A newer push to the same branch cancels the run that is still going.
concurrency:
group: ci-${{ gitea.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Generate templ components
run: go tool templ generate
- name: Vet
run: go vet ./... && go vet -tags integration ./...
- name: Unit tests
run: go test -race ./...
- name: Cross-compile for arm64
run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o /dev/null ./cmd
integration:
runs-on: ubuntu-latest
env:
# The test helper stops its own container, so the reaper container is not needed.
TESTCONTAINERS_RYUK_DISABLED: "true"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Generate templ components
run: go tool templ generate
- name: Integration tests (PostgreSQL via testcontainers)
run: go test -tags integration -race -timeout 15m ./internal/db/... ./internal/backend/...
# Checks that the image builds. Runs in parallel with the tests; nothing is pushed.
docker:
if: ${{ !startsWith(gitea.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea
uses: docker/login-action@v3
with:
registry: gitea.sanplex.xyz
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN }}
- name: Build (amd64)
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: false
cache-from: type=gha
cache-to: type=gha,mode=max,ignore-error=true
# Only for version tags, and only when all tests have passed.
publish:
if: ${{ startsWith(gitea.ref, 'refs/tags/v') }}
needs: [test, integration]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea
uses: docker/login-action@v3
with:
registry: gitea.sanplex.xyz
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN }}
- name: Build and push (amd64 + arm64)
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
cache-from: type=gha
tags: |
gitea.sanplex.xyz/sansan/musicserver:${{ gitea.ref_name }}
gitea.sanplex.xyz/sansan/musicserver:latest
-42
View File
@@ -1,42 +0,0 @@
name: Build
#on:
# release:
# types: [published]
on:
push:
branches:
- main
- develop
jobs:
# test:
# runs-on: ubuntu-latest
# steps:
# - run: echo "The release ${{ gitea.ref }} ${{ gitea.ref_name }} was published"
build:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea
uses: docker/login-action@v2
with:
registry: gitea.sanplex.xyz
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN }}
- name: Build
uses: https://github.com/docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: false
#tags: "gitea.sanplex.xyz/sansan/musicserver:${{gitea.ref_name}}, gitea.sanplex.xyz/sansan/musicserver:latest"
-41
View File
@@ -1,41 +0,0 @@
name: Publish
#on:
# release:
# types: [published]
on:
push:
branches:
- '*.*'
jobs:
# test:
# runs-on: ubuntu-latest
# steps:
# - run: echo "The release ${{ gitea.ref }} ${{ gitea.ref_name }} was published"
publish:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3
with:
config-inline: |
[registry."gitea.sanplex.xyz/sansan"]
http = true
insecure = true
- name: Login to Gitea
uses: docker/login-action@v2
with:
registry: gitea.sanplex.xyz
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN }}
- name: Build and push Docker image
uses: https://github.com/docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: "gitea.sanplex.xyz/sansan/musicserver:${{gitea.ref_name}}, gitea.sanplex.xyz/sansan/musicserver:latest"
-47
View File
@@ -1,47 +0,0 @@
name: Integration Tests
on:
workflow_dispatch: # Manual trigger only
jobs:
integration-test:
runs-on: ubuntu-latest
container:
image: golang:1.25
options: --privileged
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: music_server_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: go mod download
- name: Install testcontainers
run: go install github.com/testcontainers/testcontainers-go@latest
- name: Run integration tests
env:
DB_HOST: postgres
DB_PORT: 5432
DB_USERNAME: testuser
DB_PASSWORD: testpass
DB_NAME: music_server_test
MUSIC_PATH: ./testMusic
CHARACTERS_PATH: ./testCharacters
run: go test -v -timeout 30m ./...
+3 -5
View File
@@ -3,12 +3,10 @@ tmp
.idea .idea
*templ.go *templ.go
conf.yaml conf.yaml
output.css
compose.yaml compose.yaml
tailwindcss
.env .env
node_modules
package.json
package-lock.json
cpu.pprof cpu.pprof
main main
# Database dumps
*_dump.sql
-8
View File
@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="Go" enabled="true">
<buildTags>
<option name="customFlags">
<array>
<option value="prod" />
</array>
</option>
</buildTags>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
-36
View File
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="music_prod@192.168.86.100" uuid="e63caf71-2b1a-400d-9cc4-4b87b1f9b807">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://192.168.86.100:5432/music_prod</jdbc-url>
</data-source>
<data-source source="LOCAL" name="music_test@192.168.86.100" uuid="d063e147-d7f0-48db-9f04-57a108261ef1">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://192.168.86.100:5432/music_test</jdbc-url>
</data-source>
<data-source source="LOCAL" name="music_test@ssh.sanplex.xyz" uuid="a9cf3eb8-abc2-479b-9f27-12b4cbbfc321">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://ssh.sanplex.xyz:9432/music_test</jdbc-url>
</data-source>
<data-source source="LOCAL" name="music_prod@ssh.sanplex.xyz" uuid="20917ace-4e9a-4273-bdb7-13ebc774cbba">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://ssh.sanplex.xyz:9432/music_prod</jdbc-url>
</data-source>
<data-source source="LOCAL" name="music_test2@localhost" uuid="a423ab0a-55b0-42e1-8070-25d8ef34bfac">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/music_test2</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/MusicServer.iml" filepath="$PROJECT_DIR$/.idea/MusicServer.iml" />
</modules>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+31 -26
View File
@@ -1,36 +1,41 @@
# Stage 1: Build backend # The frontend is built and published separately; override to pin a version.
FROM golang:1.25-alpine as build_go ARG FRONTEND_IMAGE=gitea.sanplex.xyz/sansan/musicfrontend:latest
RUN apk add --no-cache curl
# Stage 1: Build the backend on the build machine and cross-compile for the target platform,
# so multi-platform builds need no emulation.
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS build
ARG TARGETOS TARGETARCH
WORKDIR /app WORKDIR /app
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN go mod download RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY cmd/ ./cmd/ COPY cmd/ ./cmd/
COPY internal/ ./internal/ COPY internal/ ./internal/
RUN find . -name "*_test.go" -delete && \ RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
find . -name "integration_test.go" -delete go tool templ generate
RUN go install github.com/a-h/templ/cmd/templ@latest RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
RUN templ generate CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
RUN go build -o main cmd/main.go go build -trimpath -ldflags="-s -w" -tags timetzdata -o main ./cmd
# Stage 2: Final image # Stage 2: Frontend static files (the same for every platform)
FROM golang:1.25-alpine FROM --platform=$BUILDPLATFORM ${FRONTEND_IMAGE} AS frontend
# Empty API_HOSTNAME makes the frontend use relative paths
RUN echo "window.__RUNTIME_CONFIG__ = { API_HOSTNAME: '' };" > /usr/share/nginx/html/config.js
# Stage 3: Final image. No RUN steps here, so no emulation is needed for other architectures.
FROM alpine:3.22
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
WORKDIR /app
COPY --from=build /app/main .
COPY --from=frontend /usr/share/nginx/html /frontend
COPY ./songs/ ./songs/
# The music volume must be readable by uid 1000, and writable for the .id file cleanup during sync.
USER 1000:1000
EXPOSE 8080 EXPOSE 8080
VOLUME /sorted VOLUME /sorted
VOLUME /characters VOLUME /characters
COPY --from=build_go /app/main . # Required: DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_NAME, MUSIC_PATH, CHARACTERS_PATH
COPY --from=gitea.sanplex.xyz/sansan/musicfrontend:latest /usr/share/nginx/html /frontend ENV PORT=8080
# Generate config.js with empty API_HOSTNAME (relative paths)
RUN echo "window.__RUNTIME_CONFIG__ = { API_HOSTNAME: '' };" > /frontend/config.js
COPY ./songs/ ./songs/
ENV PORT 8080 CMD ["./main"]
ENV DB_HOST ""
ENV DB_PORT ""
ENV DB_USERNAME ""
ENV DB_PASSWORD ""
ENV DB_NAME ""
ENV MUSIC_PATH ""
ENV CHARACTERS_PATH ""
CMD ./main
-8
View File
@@ -1,8 +0,0 @@
application: musicserver
version: 2.2.0
runtime: go115
api_version: go1
handlers:
- url: /.*
script: _go_app
+446 -220
View File
File diff suppressed because it is too large. Load diff
+435 -161
View File
File diff suppressed because it is too large. Load diff
+306 -126
View File
@@ -1,4 +1,39 @@
basePath: /
definitions: definitions:
backend.Song:
properties:
file_name:
type: string
name:
type: string
soundtrack_name:
type: string
soundtrack_uuid:
type: string
times_played:
type: integer
uuid:
type: string
type: object
backend.SongInfo:
properties:
CurrentlyPlaying:
type: boolean
Song:
type: string
SongNo:
type: integer
SongPlayed:
type: integer
SongUuid:
type: string
Soundtrack:
type: string
SoundtrackPlayed:
type: integer
SoundtrackUuid:
type: string
type: object
backend.SongInfoForStats: backend.SongInfoForStats:
properties: properties:
file_name: file_name:
@@ -7,13 +42,47 @@ definitions:
type: string type: string
song_name: song_name:
type: string type: string
song_uuid:
type: string
soundtrack_id: soundtrack_id:
type: integer type: integer
soundtrack_name: soundtrack_name:
type: string type: string
soundtrack_uuid:
type: string
times_played: times_played:
type: integer type: integer
type: object type: object
backend.Soundtrack:
properties:
added:
type: string
last_played:
type: string
name:
type: string
times_played:
type: integer
uuid:
type: string
type: object
backend.SoundtrackWithSongList:
properties:
added:
type: string
last_played:
type: string
name:
type: string
songs:
items:
$ref: '#/definitions/backend.Song'
type: array
times_played:
type: integer
uuid:
type: string
type: object
backend.SoundtrackWithSongs: backend.SoundtrackWithSongs:
properties: properties:
songs: songs:
@@ -28,6 +97,8 @@ definitions:
type: string type: string
soundtrack_played: soundtrack_played:
type: integer type: integer
soundtrack_uuid:
type: string
type: object type: object
backend.StatisticsSummary: backend.StatisticsSummary:
properties: properties:
@@ -46,6 +117,35 @@ definitions:
total_soundtracks: total_soundtracks:
type: integer type: integer
type: object type: object
backend.SyncResponse:
properties:
catched_errors:
items:
type: string
type: array
soundtracks_added:
items:
type: string
type: array
soundtracks_changed_content:
items:
type: string
type: array
soundtracks_changed_title:
additionalProperties:
type: string
type: object
soundtracks_re_added:
items:
type: string
type: array
soundtracks_removed:
items:
type: string
type: array
total_time:
type: string
type: object
backend.VersionData: backend.VersionData:
properties: properties:
changelog: changelog:
@@ -65,7 +165,7 @@ definitions:
description: 'Optional: "web", "mobile", "api"' description: 'Optional: "web", "mobile", "api"'
type: string type: string
password: password:
description: 'Required: API password' description: API password, required when API_PASSWORD is set
type: string type: string
type: object type: object
server.TokenResponse: server.TokenResponse:
@@ -78,15 +178,126 @@ definitions:
type: string type: string
type: object type: object
info: info:
contact: {} contact:
email: zarnor91@gmail.com
name: Sebastian Olsson
description: API for the MusicServer application
license:
name: MIT
url: http://opensource.org/licenses/MIT
termsOfService: http://sanplex.xyz/terms/
title: MusicServer API
version: "1.0"
paths: paths:
/api/v1/songs/{uuid}:
get:
description: Returns information about a song
parameters:
- description: Song UUID
in: path
name: uuid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/backend.Song'
"400":
description: invalid uuid
schema:
type: string
"404":
description: not found
schema:
type: string
summary: Get song
tags:
- library
/api/v1/songs/{uuid}/file:
get:
description: Returns the audio file of a song. Supports Range requests.
parameters:
- description: Song UUID
in: path
name: uuid
required: true
type: string
produces:
- audio/mpeg
responses:
"200":
description: OK
schema:
type: file
"206":
description: Partial Content
schema:
type: file
"400":
description: invalid uuid
schema:
type: string
"404":
description: not found
schema:
type: string
"423":
description: Syncing is in progress
schema:
type: string
summary: Stream song
tags:
- library
/api/v1/soundtracks:
get:
description: Returns all soundtracks with their UUIDs, sorted by name
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/backend.Soundtrack'
type: array
summary: List soundtracks
tags:
- library
/api/v1/soundtracks/{uuid}:
get:
description: Returns a soundtrack with its songs
parameters:
- description: Soundtrack UUID
in: path
name: uuid
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/backend.SoundtrackWithSongList'
"400":
description: invalid uuid
schema:
type: string
"404":
description: not found
schema:
type: string
summary: Get soundtrack
tags:
- library
/api/v1/statistics/songs/least-played: /api/v1/statistics/songs/least-played:
get: get:
consumes:
- application/json
description: Returns the top N least played songs with their soundtrack info description: Returns the top N least played songs with their soundtrack info
parameters: parameters:
- description: 'Number of results (default: 10)' - description: 'Number of results (default: 10, max: 100)'
in: query in: query
name: limit name: limit
type: integer type: integer
@@ -116,11 +327,9 @@ paths:
- statistics - statistics
/api/v1/statistics/songs/most-played: /api/v1/statistics/songs/most-played:
get: get:
consumes:
- application/json
description: Returns the top N most played songs with their soundtrack info description: Returns the top N most played songs with their soundtrack info
parameters: parameters:
- description: 'Number of results (default: 10)' - description: 'Number of results (default: 10, max: 100)'
in: query in: query
name: limit name: limit
type: integer type: integer
@@ -150,11 +359,9 @@ paths:
- statistics - statistics
/api/v1/statistics/soundtracks/last-played: /api/v1/statistics/soundtracks/last-played:
get: get:
consumes: description: Returns the most recently played soundtracks with their songs
- application/json
description: Returns the most recently played soundtracks
parameters: parameters:
- description: 'Number of results (default: 10)' - description: 'Number of results (default: 10, max: 100)'
in: query in: query
name: limit name: limit
type: integer type: integer
@@ -184,11 +391,9 @@ paths:
- statistics - statistics
/api/v1/statistics/soundtracks/least-played: /api/v1/statistics/soundtracks/least-played:
get: get:
consumes:
- application/json
description: Returns the top N least played soundtracks with their songs description: Returns the top N least played soundtracks with their songs
parameters: parameters:
- description: 'Number of results (default: 10)' - description: 'Number of results (default: 10, max: 100)'
in: query in: query
name: limit name: limit
type: integer type: integer
@@ -218,11 +423,9 @@ paths:
- statistics - statistics
/api/v1/statistics/soundtracks/most-played: /api/v1/statistics/soundtracks/most-played:
get: get:
consumes:
- application/json
description: Returns the top N most played soundtracks with their songs description: Returns the top N most played soundtracks with their songs
parameters: parameters:
- description: 'Number of results (default: 10)' - description: 'Number of results (default: 10, max: 100)'
in: query in: query
name: limit name: limit
type: integer type: integer
@@ -252,10 +455,7 @@ paths:
- statistics - statistics
/api/v1/statistics/soundtracks/never-played: /api/v1/statistics/soundtracks/never-played:
get: get:
consumes: description: Returns all soundtracks that have never been played
- application/json
description: Returns all soundtracks that have never been played (times_played
= 0)
produces: produces:
- application/json - application/json
responses: responses:
@@ -276,12 +476,9 @@ paths:
- statistics - statistics
/api/v1/statistics/soundtracks/oldest-played: /api/v1/statistics/soundtracks/oldest-played:
get: get:
consumes: description: Returns the least recently played soundtracks with their songs
- application/json
description: Returns the least recently played soundtracks (that have been played
at least once)
parameters: parameters:
- description: 'Number of results (default: 10)' - description: 'Number of results (default: 10, max: 100)'
in: query in: query
name: limit name: limit
type: integer type: integer
@@ -311,9 +508,7 @@ paths:
- statistics - statistics
/api/v1/statistics/summary: /api/v1/statistics/summary:
get: get:
consumes: description: Returns overall play statistics
- application/json
description: Returns overall statistics about the music library
produces: produces:
- application/json - application/json
responses: responses:
@@ -332,8 +527,6 @@ paths:
- statistics - statistics
/api/v1/token: /api/v1/token:
delete: delete:
consumes:
- application/json
description: Deletes the current session token description: Deletes the current session token
parameters: parameters:
- description: Bearer token - description: Bearer token
@@ -368,8 +561,8 @@ paths:
post: post:
consumes: consumes:
- application/json - application/json
description: Returns a new session token for API access. Requires valid API description: Returns a new session token for API access. Requires the API password
password. when one is configured.
parameters: parameters:
- description: Password and client type - description: Password and client type
in: body in: body
@@ -396,6 +589,12 @@ paths:
additionalProperties: additionalProperties:
type: string type: string
type: object type: object
"429":
description: Too Many Requests
schema:
additionalProperties:
type: string
type: object
"500": "500":
description: Internal Server Error description: Internal Server Error
schema: schema:
@@ -407,8 +606,6 @@ paths:
- auth - auth
/api/v1/token/cleanup: /api/v1/token/cleanup:
post: post:
consumes:
- application/json
description: Removes all expired session tokens from the database description: Removes all expired session tokens from the database
parameters: parameters:
- description: Bearer token - description: Bearer token
@@ -422,7 +619,8 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
additionalProperties: true additionalProperties:
type: string
type: object type: object
"401": "401":
description: Unauthorized description: Unauthorized
@@ -441,29 +639,34 @@ paths:
- auth - auth
/character: /character:
get: get:
consumes:
- application/json
description: Returns the image for a specific character description: Returns the image for a specific character
parameters: parameters:
- description: Character name - description: Character file name
in: query in: query
name: name name: name
required: true required: true
type: string type: string
produces: produces:
- image/png - image/png
- image/jpeg
responses: responses:
"200": "200":
description: OK description: OK
schema: schema:
type: file type: file
"400":
description: invalid name
schema:
type: string
"404":
description: not found
schema:
type: string
summary: Get character image summary: Get character image
tags: tags:
- characters - characters
/characters: /characters:
get: get:
consumes:
- application/json
description: Returns a list of all available characters description: Returns a list of all available characters
produces: produces:
- application/json - application/json
@@ -479,9 +682,7 @@ paths:
- characters - characters
/download: /download:
get: get:
consumes: description: Returns the latest version of the client application
- application/json
description: Checks for the latest version of the application
produces: produces:
- application/json - application/json
responses: responses:
@@ -489,26 +690,34 @@ paths:
description: OK description: OK
schema: schema:
type: string type: string
"502":
description: upstream service unavailable
schema:
type: string
summary: Check for latest version summary: Check for latest version
tags: tags:
- download - download
/download/linux: /download/linux:
get: get:
description: Redirects to download the latest Linux version description: Redirects to download the latest Linux version
produces:
- application/octet-stream
responses: responses:
"302": "302":
description: Found description: Found
schema: schema:
type: string type: string
"404":
description: not found
schema:
type: string
"502":
description: upstream service unavailable
schema:
type: string
summary: Download latest Linux version summary: Download latest Linux version
tags: tags:
- download - download
/download/list: /download/list:
get: get:
consumes:
- application/json
description: Lists all assets available for the latest version description: Lists all assets available for the latest version
produces: produces:
- application/json - application/json
@@ -519,26 +728,34 @@ paths:
items: items:
type: string type: string
type: array type: array
"502":
description: upstream service unavailable
schema:
type: string
summary: List assets of latest version summary: List assets of latest version
tags: tags:
- download - download
/download/windows: /download/windows:
get: get:
description: Redirects to download the latest Windows version description: Redirects to download the latest Windows version
produces:
- application/octet-stream
responses: responses:
"302": "302":
description: Found description: Found
schema: schema:
type: string type: string
"404":
description: not found
schema:
type: string
"502":
description: upstream service unavailable
schema:
type: string
summary: Download latest Windows version summary: Download latest Windows version
tags: tags:
- download - download
/health: /health:
get: get:
consumes:
- application/json
description: Returns the health status of the server description: Returns the health status of the server
produces: produces:
- application/json - application/json
@@ -546,21 +763,21 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
type: string additionalProperties:
type: string
type: object
summary: Check server health summary: Check server health
tags: tags:
- health - health
/music: /music:
get: get:
consumes: description: Returns the song at the given position in the queue
- application/json
description: Returns a specific song by name
parameters: parameters:
- description: Song name - description: Song number in the queue
in: query in: query
name: song name: song
required: true required: true
type: string type: integer
produces: produces:
- audio/mpeg - audio/mpeg
responses: responses:
@@ -568,8 +785,12 @@ paths:
description: OK description: OK
schema: schema:
type: file type: file
"206":
description: Partial Content
schema:
type: file
"400": "400":
description: song can't be empty description: song must be a number
schema: schema:
type: string type: string
"404": "404":
@@ -585,12 +806,10 @@ paths:
- music - music
/music/addPlayed: /music/addPlayed:
get: get:
consumes: description: Marks the current song as played
- application/json
description: Adds the latest song to the played list
responses: responses:
"204": "200":
description: "" description: OK
"423": "423":
description: Syncing is in progress description: Syncing is in progress
schema: schema:
@@ -600,12 +819,10 @@ paths:
- music - music
/music/addQue: /music/addQue:
get: get:
consumes:
- application/json
description: Adds the latest song to the queue description: Adds the latest song to the queue
responses: responses:
"204": "200":
description: "" description: OK
"423": "423":
description: Syncing is in progress description: Syncing is in progress
schema: schema:
@@ -615,8 +832,6 @@ paths:
- music - music
/music/all/order: /music/all/order:
get: get:
consumes:
- application/json
description: Returns a list of all soundtracks in order description: Returns a list of all soundtracks in order
produces: produces:
- application/json - application/json
@@ -625,8 +840,7 @@ paths:
description: OK description: OK
schema: schema:
items: items:
additionalProperties: true type: string
type: object
type: array type: array
"423": "423":
description: Syncing is in progress description: Syncing is in progress
@@ -637,8 +851,6 @@ paths:
- music - music
/music/all/random: /music/all/random:
get: get:
consumes:
- application/json
description: Returns a list of all soundtracks in random order description: Returns a list of all soundtracks in random order
produces: produces:
- application/json - application/json
@@ -647,8 +859,7 @@ paths:
description: OK description: OK
schema: schema:
items: items:
additionalProperties: true type: string
type: object
type: array type: array
"423": "423":
description: Syncing is in progress description: Syncing is in progress
@@ -659,8 +870,6 @@ paths:
- music - music
/music/info: /music/info:
get: get:
consumes:
- application/json
description: Returns information about the current song description: Returns information about the current song
produces: produces:
- application/json - application/json
@@ -668,15 +877,12 @@ paths:
"200": "200":
description: OK description: OK
schema: schema:
additionalProperties: true $ref: '#/definitions/backend.SongInfo'
type: object
summary: Get current song info summary: Get current song info
tags: tags:
- music - music
/music/list: /music/list:
get: get:
consumes:
- application/json
description: Returns a list of played songs description: Returns a list of played songs
produces: produces:
- application/json - application/json
@@ -685,8 +891,7 @@ paths:
description: OK description: OK
schema: schema:
items: items:
additionalProperties: true $ref: '#/definitions/backend.SongInfo'
type: object
type: array type: array
summary: Get played songs list summary: Get played songs list
tags: tags:
@@ -714,20 +919,16 @@ paths:
- music - music
/music/played: /music/played:
put: put:
consumes: description: Marks the song at the given queue position as played
- application/json
description: Marks a song as played by its ID
parameters: parameters:
- description: Song ID - description: Song number in the queue
in: query in: query
name: song name: song
required: true required: true
type: integer type: integer
produces:
- application/json
responses: responses:
"204": "200":
description: "" description: OK
"400": "400":
description: Bad Request description: Bad Request
schema: schema:
@@ -783,7 +984,7 @@ paths:
- music - music
/music/rand/classic: /music/rand/classic:
get: get:
description: Returns a random song from the classic selection description: Returns a random song where every song has the same chance
produces: produces:
- audio/mpeg - audio/mpeg
responses: responses:
@@ -804,7 +1005,8 @@ paths:
- music - music
/music/rand/low: /music/rand/low:
get: get:
description: Returns a random song with low chance selection description: Returns a random song, favouring soundtracks that have been played
less
produces: produces:
- audio/mpeg - audio/mpeg
responses: responses:
@@ -825,12 +1027,10 @@ paths:
- music - music
/music/reset: /music/reset:
get: get:
consumes:
- application/json
description: Resets the music state description: Resets the music state
responses: responses:
"204": "200":
description: "" description: OK
"423": "423":
description: Syncing is in progress description: Syncing is in progress
schema: schema:
@@ -860,10 +1060,8 @@ paths:
tags: tags:
- music - music
/sync: /sync:
get: post:
consumes: description: Starts syncing soundtracks that have changed
- application/json
description: Starts syncing soundtracks with only new changes
produces: produces:
- application/json - application/json
responses: responses:
@@ -879,9 +1077,7 @@ paths:
tags: tags:
- sync - sync
/sync/full: /sync/full:
get: post:
consumes:
- application/json
description: Starts a full sync of all soundtracks description: Starts a full sync of all soundtracks
produces: produces:
- application/json - application/json
@@ -899,24 +1095,20 @@ paths:
- sync - sync
/sync/progress: /sync/progress:
get: get:
consumes: description: Returns the progress of the running sync, or the result of the
- application/json last sync
description: Returns the current sync progress or result
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema:
additionalProperties: true $ref: '#/definitions/backend.SyncResponse'
type: object
summary: Get sync progress summary: Get sync progress
tags: tags:
- sync - sync
/sync/reset: /sync/reset:
get: post:
consumes:
- application/json
description: Resets the soundtracks database by deleting all soundtracks and description: Resets the soundtracks database by deleting all soundtracks and
songs songs
produces: produces:
@@ -935,8 +1127,6 @@ paths:
- sync - sync
/version: /version:
get: get:
consumes:
- application/json
description: get latest version info description: get latest version info
produces: produces:
- application/json - application/json
@@ -945,17 +1135,11 @@ paths:
description: OK description: OK
schema: schema:
$ref: '#/definitions/backend.VersionData' $ref: '#/definitions/backend.VersionData'
"404":
description: Not Found
schema:
type: string
summary: Getting the latest version of the backend summary: Getting the latest version of the backend
tags: tags:
- version - version
/version/history: /version/history:
get: get:
consumes:
- application/json
description: get version history description: get version history
produces: produces:
- application/json - application/json
@@ -966,10 +1150,6 @@ paths:
items: items:
$ref: '#/definitions/backend.VersionData' $ref: '#/definitions/backend.VersionData'
type: array type: array
"404":
description: Not Found
schema:
type: string
summary: Getting the version history of the backend summary: Getting the version history of the backend
tags: tags:
- version - version
-21
View File
@@ -1,21 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-24
View File
@@ -1,24 +0,0 @@
# music-randomizer-vue
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
-26
View File
@@ -1,26 +0,0 @@
const express = require("express");
const path = require("path");
const routes = require("./routes.js");
const app = express();
const cors = require("cors");
/* const bodyParser = require("body-parser"); */
const PORT = process.env.PORT || 5005;
const mainUrl = "test";
console.log(`Server started on port ${PORT}`);
//app.use(bodyParser.json());
//Routes
/* app.use(express.static(path.join(__dirname, "public"))); */
/* app.use(express.static(path.join(__dirname, "assets"))); */
app.use(express.static(path.join(__dirname)));
app.use(express.static(path.join(__dirname, "dist")));
/* app.use(express.static(path.join(__dirname, "public"))); */
/* console.log(__dirname); */
app.use(cors());
app.use(routes);
app.listen(PORT, "0.0.0.0");
-12192
View File
File diff suppressed because it is too large. Load diff
-60
View File
@@ -1,60 +0,0 @@
{
"name": "music-randomizer-vue",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.7.2",
"core-js": "^3.37.1",
"cors": "^2.8.5",
"express": "^4.19.2",
"vue": "^3.4.31",
"vue-axios": "^3.5.2",
"vuex": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/compiler-sfc": "^3.4.31",
"@babel/eslint-parser": "^7.25.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.27.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser",
"requireConfigFile": false,
"ecmaVersion": 2020,
"sourceType": "module",
"ecmaFeatures": {
"globalReturn": false,
"impliedStrict": true,
"jsx": true
},
"babelOptions": {
"presets": ["@babel/preset-env"]
}
},
"rules": {
"no-debugger": 1
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/></svg>

Before

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 B

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-19
View File
@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>

Before

Width:  |  Height:  |  Size: 212 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><path d="M0 0h24v24H0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>

Before

Width:  |  Height:  |  Size: 201 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><g><rect fill="none" height="24" width="24"/></g><g><path d="M13,8c0-2.21-1.79-4-4-4S5,5.79,5,8s1.79,4,4,4S13,10.21,13,8z M15,10v2h3v3h2v-3h3v-2h-3V7h-2v3H15z M1,18v2h16v-2 c0-2.66-5.33-4-8-4S1,15.34,1,18z"/></g></svg>

Before

Width:  |  Height:  |  Size: 352 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><g><rect fill="none" height="24" width="24"/></g><g><path d="M14,8c0-2.21-1.79-4-4-4S6,5.79,6,8s1.79,4,4,4S14,10.21,14,8z M17,10v2h6v-2H17z M2,18v2h16v-2c0-2.66-5.33-4-8-4 S2,15.34,2,18z"/></g></svg>

Before

Width:  |  Height:  |  Size: 333 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Whitespace-only changes.
-147
View File
@@ -1,147 +0,0 @@
<template>
<the-header @start-sound-test="startSoundTest"></the-header>
<the-main ref="theMain"></the-main>
</template>
<script>
import TheHeader from "./components/layout/TheHeader.vue";
import TheMain from "./components/layout/TheMain.vue";
export default {
components: {
TheHeader,
TheMain,
},
data() {
return {
screenWidth: 1000,
};
},
computed: {
displayWhenDesktop() {
if (this.screenWidth > 600) {
return true;
} else {
return false;
}
},
},
methods: {
startSoundTest() {
this.$refs.theMain.startSoundTest();
},
},
mounted() {
this.screenWidth = window.innerWidth;
window.addEventListener("resize", () => (this.screenWidth = window.innerWidth));
},
};
</script>
<style>
* {
margin: 0;
padding: 0;
font-family: Helvetica;
}
button {
background: rgb(54, 54, 54);
border-radius: 10px;
border-color: #ff9c00;
color: #ff9c00;
font-size: 1em;
padding: 10px;
outline: none;
}
.app {
display: flex;
height: 100vh;
}
body {
background-color: #555;
}
/* Global modal stuff */
.modalAni-enter-from,
.modalAni-leave-to {
opacity: 0;
transform: translateY(-20px);
}
.modalAni-enter-to,
.modalAni-leave-from {
opacity: 1;
}
.modalAni-enter-active,
.modalAni-leave-active {
transition: all 0.25s ease-out;
}
.modal {
position: fixed; /* Stay in place */
z-index: 10; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: hidden;
background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0, 0, 0, 0.5); /* Black w/ opacity */
}
.modalContainer {
background-color: #eeeeee;
margin: 10% auto; /* 15% from the top and centered */
border: 1px solid #888;
width: 50%; /* Could be more or less, depending on screen size */
}
.modalWrapper {
display: flex;
position: relative;
flex-wrap: wrap;
padding: 10px 40px;
}
.modalWrapper:last-child {
margin-bottom: 50px;
}
.closeModalImg {
position: absolute;
right: 8px;
top: 8px;
cursor: pointer;
opacity: 70%;
}
.modal h1 {
color: black;
margin-left: auto;
margin-right: auto;
padding-top: 10px;
font-size: 1.9rem;
}
.modal p {
color: black;
}
@media only screen and (max-width: 1000px) {
.modalContainer {
margin: 10% auto; /* 15% from the top and centered */
width: 93%; /* Could be more or less, depending on screen size */
}
.modalWrapper {
padding: 10px 10px;
}
.modalWrapper:last-child {
margin-bottom: 10px;
}
.modal h1 {
padding-top: 10px;
font-size: 1.6rem;
}
}
</style>
-3
View File
@@ -1,3 +0,0 @@
export default {
hostname: "$HOSTNAME",
}
@@ -1,95 +0,0 @@
<template>
<!-- <div class="currentlyPlayingDiv">
<div class="textWrapper">
<transition-group name="displayingInfo" appear>
<p v-if="currentTrackHidden" :key="1">????????</p>
<p v-else :key="2">{{ currentGame }}</p>
<p v-if="currentTrackHidden" :key="3">??????</p>
<p v-else :key="4">{{ currentTrack }}</p>
</transition-group>
</div>
</div> -->
<div class="currentlyPlayingDiv">
<transition name="displayingInfo">
<div v-if="currentTrackHidden" class="textWrapper">
<p>????????</p>
<p>??????</p>
</div>
<div v-else class="textWrapper">
<p>{{ currentGame }}</p>
<p>{{ currentTrack }}</p>
</div>
</transition>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(["currentGame", "currentTrack", "currentTrackHidden"]),
},
};
</script>
<style scoped>
.displayingInfo-enter-from,
.displayingInfo-leave-to {
opacity: 0;
}
.displayingInfo-enter-to,
.displayingInfo-leave-from {
opacity: 1;
}
.displayingInfo-enter-active {
transition: opacity 0.7s ease-out;
}
.displayingInfo-leave-active {
display: none;
}
.currentlyPlayingDiv {
display: flex;
flex-wrap: wrap;
justify-content: center;
width: 60vw;
height: 27vh;
margin-right: 0.5vw;
color: #ff8800;
background-color: #333;
border-radius: 10px;
text-align: center;
overflow: hidden;
}
.currentlyPlayingDiv .textWrapper {
padding: 0.75vw;
}
.currentlyPlayingDiv .textWrapper p {
width: 100%;
font-size: 2.3em;
}
.currentlyPlayingDiv p:first-child {
margin-top: 1.5vw;
margin-bottom: 1.5vw;
font-size: 2.8em;
}
@media only screen and (max-width: 1000px) {
.currentlyPlayingDiv {
width: 100vw;
height: 35vw;
margin-right: 0;
border-radius: 0px;
align-content: center;
}
.currentlyPlayingDiv .textWrapper p {
font-size: 1.1em;
}
.currentlyPlayingDiv p:first-child {
margin-bottom: 2vw;
font-size: 1.4em;
}
}
</style>
@@ -1,170 +0,0 @@
<template>
<div class="inspirationDiv">
<div class="inspirationHeader">
<div class="inspirationTitle">
<h1>Inspiration</h1>
</div>
<div class="searchInput">
<input
v-model="searchInputText"
type="text"
@input="searchGame()"
ref="inputField"
/>
</div>
<button @click="clearSearch()">Clear</button>
</div>
<transition-group
tag="ul"
name="inspirationList"
class="inspirationList"
ref="inspirationList"
>
<li v-for="game in showingGamesList" class="inspirationEntry" :key="game">
{{ game }}
</li>
</transition-group>
</div>
</template>
<script>
import arne from '../../arne.js'
import {mapState} from "vuex";
export default {
data() {
return {
allGamesList: [],
showingGamesList: [],
scrollDown: true,
searchInputText: "",
};
},
computed: {
...mapState(["reloadGamesList"]),
},
watch: {
reloadGamesList(newValue) {
if (newValue) {
this.reloadGames();
this.$store.dispatch("reloadGamesList", false);
}
}
},
methods: {
clearSearch() {
this.searchInputText = "";
this.searchGame();
},
searchGame() {
this.showingGamesList = [];
for (const game of this.allGamesList) {
if (this.searchInputText === "" ||
game.toLowerCase().replace(/\s/g, "")
.includes(this.searchInputText.toLowerCase().replace(/\s/g, ""))) {
this.showingGamesList.push(game);
}
}
if (this.searchInputText.replace(/\s/g, "") !== "") {
this.showingGamesList.sort((n1, n2) => {
if (n1 > n2) {
return 1;
}
if (n1 < n2) {
return -1;
}
return 0;
});
}
},
scrollInspiration() {
let inspirationListDOM = document.querySelector(".inspirationList");
let scrollSpeed = 1;
let currentScrollLocation = 0;
currentScrollLocation = inspirationListDOM.scrollTop;
this.scrollDown
? inspirationListDOM.scrollBy(0, scrollSpeed)
: inspirationListDOM.scrollBy(0, -scrollSpeed);
if (currentScrollLocation === inspirationListDOM.scrollTop) {
this.scrollDown = !this.scrollDown;
}
},
reloadGames() {
this.axios({
method: "get",
url: `${arne.hostname}/music/all`,
})
.then((response) => {
this.allGamesList = response.data;
this.searchInputText = "";
this.searchGame();
this.$store.dispatch("updateHowManyGames", this.allGamesList.length);
})
.catch(function(error) {
console.log(error);
});
}
},
mounted() {
this.reloadGames();
window.setInterval(() => {
this.scrollInspiration();
}, 40);
},
};
</script>
<style scoped>
.inspirationDiv {
width: 38vw;
height: 20vh;
color: white;
}
.inspirationTitle {
display: flex;
flex: auto;
align-items: flex-start;
justify-content: flex-start;
padding: 5px 10px;
}
.searchInput {
display: flex;
flex: auto;
align-items: flex-end;
justify-content: flex-end;
padding: 0 10px;
}
.inspirationHeader {
display: flex;
background-color: #333;
height: 5vh;
justify-content: center;
align-items: center;
}
.inspirationList {
display: inline-block;
background-color: rgb(66, 66, 66);
width: 38vw;
height: 22vh;
list-style: none;
overflow: hidden;
}
.inspirationEntry {
width: 100%;
max-width: 38vw;
padding: 0.3vh 0.8vh;
font-size: 1.2rem;
}
@media only screen and (max-width: 1000px) {
.inspirationDiv {
display: none;
}
}
</style>
@@ -1,78 +0,0 @@
<template>
<transition name="modalAni">
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
<div class="modalContainer">
<div class="modalWrapper">
<img
class="closeModalImg"
src="cancel-black-36dp.svg"
alt="closeModalIMG"
@click="closeModal"
/>
<h1>Music Player Randomizer v0.1</h1>
<p class="descriptionText">
Try your video game music knowledge with this VGM randomizer,
invite your friends and see who is the best.
</p>
<p class="creditText">
Back-end done by Sebastian Olsson,
<a href="https://twitter.com/Raida91">Twitter</a>
</p>
<p class="creditText">
Front-end done by Peter Johansson,
<a href="https://twitter.com/WebDevPJ">Twitter</a>
</p>
<!-- <p class="patchNotesText">
Full patch notes can be found
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">here</a>
</p> -->
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
data() {
return {
show: false,
};
},
methods: {
closeModal() {
this.show = false;
},
openModal() {
this.show = true;
},
checkIfClickShouldCloseModal(event) {
if (event.target.classList[0] === "modal") {
this.closeModal();
}
},
},
};
</script>
<style scoped>
p {
font-size: 1.2rem;
width: 100%;
}
.descriptionText {
margin-top: 35px;
}
.creditText {
margin-top: 30px;
}
.creditText:nth-child(5) {
margin-top: 0px;
}
.patchNotesText {
margin-top: 20px;
}
</style>
@@ -1,160 +0,0 @@
<template>
<transition name="modalAni">
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
<div class="modalContainer">
<div class="modalWrapper">
<img
class="closeModalImg"
src="cancel-black-36dp.svg"
alt="closeModalIMG"
@click="closeModal"
/>
<h1 @click="test">{{ playerName }}, choose your fighter!</h1>
<div class="fighterDiv">
<div class="fighter" @click="chooseFighter('Adol')">
<img src="characters/Adol.png" alt="adol" />
<p>Adol Christin</p>
</div>
<div class="fighter" @click="chooseFighter('Link')">
<img src="characters/Link.png" alt="link" />
<p>Link</p>
</div>
<div class="fighter" @click="chooseFighter('Barbarian')">
<img src="characters/Barbarian.png" alt="barbarian" />
<p>Barbarian</p>
</div>
<div class="fighter" @click="chooseFighter('Layton')">
<img src="characters/Layton.png" alt="layton" />
<p>Professor Layton</p>
</div>
<div class="fighter" @click="chooseFighter('Kiryu')">
<img src="characters/Kiryu.png" alt="kiryu" />
<p>Kazuma Kiryu</p>
</div>
<div class="fighter" @click="chooseFighter('Miles')">
<img src="characters/Miles.png" alt="miles" />
<p>Miles Edgeworth</p>
</div>
<div class="fighter" @click="chooseFighter('Lemmings')">
<img src="characters/Lemmings.png" alt="lemmings" />
<p>Lemmings</p>
</div>
<div class="fighter" @click="chooseFighter('Samus')">
<img src="characters/Samus.png" alt="samus" />
<p>Samus</p>
</div>
<div class="fighter" @click="chooseFighter('Kratos')">
<img src="characters/Kratos.png" alt="kratos" />
<p>Kratos</p>
</div>
<div class="fighter" @click="chooseFighter('Aloy')">
<img src="characters/Aloy.png" alt="aloy" />
<p>Aloy</p>
</div>
<div class="fighter" @click="chooseFighter('Sora')">
<img src="characters/Sora.png" alt="sora" />
<p>Sora</p>
</div>
<div class="fighter" @click="chooseFighter('Raiden')">
<img src="characters/Raiden.png" alt="raiden" />
<p>Raiden</p>
</div>
<div class="fighter" @click="chooseFighter('PaperMario')">
<img src="characters/PaperMario.png" alt="paper mario" />
<p>Paper Mario</p>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
data() {
return {
show: false,
playerName: "",
};
},
methods: {
closeModal() {
this.show = false;
},
openModal(playerName) {
this.playerName = playerName;
this.show = true;
},
checkIfClickShouldCloseModal(event) {
if (event.target.classList[0] === "modal") {
this.closeModal();
}
},
chooseFighter(profilePicSrc) {
let payload = {
playerName: this.playerName,
profile: "characters/" + profilePicSrc + ".png",
};
this.$store.dispatch("changePlayerProfile", payload);
this.closeModal();
},
},
};
</script>
<style scoped>
.modalContainer {
width: 80%; /* Could be more or less, depending on screen size */
}
.fighterDiv {
display: flex;
width: 100%;
justify-content: center;
margin-top: 30px;
flex-wrap: wrap;
}
.fighterDiv img {
width: 180px;
height: 90px;
}
.fighter {
display: flex;
flex-wrap: wrap;
justify-content: center;
text-align: center;
width: 14vw;
margin-top: 30px;
}
.fighter > * {
width: 100%;
}
.fighter:hover {
outline: 1px solid rgb(128, 83, 0);
outline-offset: 2px;
cursor: pointer;
}
.fighter p {
margin-top: 8px;
font-size: 1.3rem;
}
@media only screen and (max-width: 1000px) {
.modalContainer {
width: 95%; /* Could be more or less, depending on screen size */
}
.fighterDiv {
margin-top: 30px;
}
.fighterDiv img {
width: 80px;
height: 40px;
}
.fighter {
width: 25vw;
}
}
</style>
@@ -1,111 +0,0 @@
<template>
<div class="extraButtonsDiv">
<button @click="resetPlaylist">Reset playlist</button>
<button @click="resetPoints">Reset points</button>
<button @click="syncGames">Sync games</button>
<button @click="startSoundTest">Sound test</button>
</div>
</template>
<script>
import arne from '../../arne.js'
export default {
data() {
return {
emptyPlaylist: [],
};
},
methods: {
async resetPoints() {
this.$store.dispatch("resetPlayerScore");
this.$store.dispatch("resetPlayerWelcomed");
this.$store.dispatch("resetPlayerMatchPoint");
this.$store.dispatch("setRoundStarted", false);
},
async resetPlaylist() {
await this.APIresetPlaylist();
this.$store.dispatch("updatePlaylistHistory", this.emptyPlaylist);
this.$store.dispatch("setCurrentlyLoadingTrack", "N/A");
this.$store.dispatch("setCurrentTrackHidden", false);
},
async syncGames() {
await this.APIsyncGames();
await this.APIresetPlaylist();
this.$store.dispatch("resetPlayerScore");
this.$store.dispatch("resetPlayerWelcomed");
this.$store.dispatch("resetPlayerMatchPoint");
this.$store.dispatch("setRoundStarted", false);
this.$store.dispatch("updatePlaylistHistory", this.emptyPlaylist);
this.$store.dispatch("setCurrentlyLoadingTrack", "N/A");
this.$store.dispatch("setCurrentTrackHidden", false);
this.$store.dispatch("reloadGamesList", true);
},
startSoundTest() {
this.$emit("start-sound-test");
},
APIresetPlaylist() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/reset`,
})
.then(() => {
resolve();
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
APIsyncGames() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/sync`,
})
.then(() => {
resolve();
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
},
};
</script>
<style scoped>
.extraButtonsDiv {
display: flex;
margin-top: 2px;
margin-left: 10px;
}
button {
display: flex;
align-items: center;
height: 65%;
margin-right: 0.4vw;
border-radius: 10px;
border-width: 1px;
}
@media only screen and (max-width: 1000px) {
.extraButtonsDiv {
display: flex;
position: relative;
flex-wrap: wrap;
justify-content: center;
}
button {
width: 51vw;
height: 40px;
margin-bottom: 10px;
justify-content: center;
}
}
</style>
@@ -1,136 +0,0 @@
<template>
<div
v-if="show"
class="mobileMenu"
@click="checkIfClickShouldCloseMobileMenu"
>
<transition name="mobileMenuAni">
<div v-if="showContent" class="mobileMenuContainer">
<div class="mobileMenuWrapper">
<h1>Menu</h1>
<button class="settingsButton" @click="showSettingsModal">
Settings
</button>
<button class="statisticsButton" @click="showStatisticsModal">
Statistics
</button>
<button class="aboutButton" @click="showAboutModal">
About
</button>
<h1>Control</h1>
<extra-buttons
@start-sound-test="startSoundTest"
></extra-buttons>
</div>
</div>
</transition>
</div>
</template>
<script>
import extraButtons from "./extraButtons.vue";
export default {
components: {
extraButtons,
},
data() {
return {
show: false,
showContent: false,
};
},
methods: {
closeMobileMenu() {
this.showContent = false;
setTimeout(() => {
this.show = false;
}, 250);
},
openMobileMenu() {
this.show = true;
},
openMobileMenuContent() {
this.showContent = true;
},
checkIfClickShouldCloseMobileMenu(event) {
if (event.target.classList[0] === "mobileMenu") {
this.closeMobileMenu();
}
},
showSettingsModal() {
this.show = false;
this.showContent = false;
this.$emit("show-settings-modal");
},
showStatisticsModal() {
this.show = false;
this.showContent = false;
this.$emit("show-statistics-modal");
},
showAboutModal() {
this.show = false;
this.showContent = false;
this.$emit("show-about-modal");
},
startSoundTest() {
this.$emit("start-sound-test");
},
},
};
</script>
<style scoped>
.mobileMenuAni-enter-from,
.mobileMenuAni-leave-to {
opacity: 0.6;
transform: translateX(80vw);
}
.mobileMenuAni-enter-to,
.mobileMenuAni-leave-from {
opacity: 1;
transform: translateX(0vw);
}
.mobileMenuAni-enter-active,
.mobileMenuAni-leave-active {
transition: all 0.3s ease-out;
}
.mobileMenu {
position: fixed;
z-index: 8;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.5);
}
.mobileMenuContainer {
background-color: #c4c4c4;
width: 80vw;
height: 100%;
margin-left: 20%;
}
.mobileMenuWrapper {
display: flex;
position: relative;
flex-wrap: wrap;
justify-content: center;
}
h1 {
margin: 10px 0px;
width: 100%;
text-align: center;
}
button {
width: 51vw;
height: 40px;
margin-bottom: 10px;
}
</style>
@@ -1,354 +0,0 @@
<template>
<div class="playersWindowDiv">
<div @click="toggleDisplay" class="playerListTitle">
<img v-if="display" class="arrow arrowToggle" src="keyboard_arrow_up-black-36dp.svg" />
<img v-else class="arrow arrowToggle" src="keyboard_arrow_down-black-36dp.svg" />
<h1>Players</h1>
<img
src="person_add_alt_1-black-36dp.svg"
alt="addPlayer"
class="addPlayerIMG"
@click="toggleNewPlayerInput()"
/>
</div>
<transition-group tag="div" v-if="display" name="playerList" class="playerList">
<div class="player" v-for="player in listOfPlayers" :key="player.playerName">
<p>{{ player.playerName }}: {{ player.score }}</p>
<img
class="profilePicture"
:src="player.profile"
@click="openCharacterModal(player.playerName)"
/>
<button @click="changeScore(player.playerName, 1)">+1</button>
<button @click="changeScore(player.playerName, -1)">-1</button>
<img
src="person_remove-black-36dp.svg"
@click="removePlayer(player.playerName)"
alt=""
/>
</div>
<div class="newPlayerInput" v-if="showNewPlayerInput === true">
<input
v-model="newPlayerInputText"
type="text"
@keypress.enter="addNewPlayer()"
ref="inputField"
placeholder="Player name"
/>
<button @click="addNewPlayer()">Add</button>
</div>
</transition-group>
<winning-modal ref="winningModal"></winning-modal>
<character-modal ref="characterModal"></character-modal>
</div>
</template>
<script>
import { mapState } from "vuex";
import winningModal from "../items/winningModal.vue";
import characterModal from "../items/characterModal.vue";
export default {
components: {
winningModal,
characterModal,
},
data() {
return {
showNewPlayerInput: false,
newPlayerInputText: "",
display: true,
};
},
computed: {
...mapState(["listOfPlayers", "winningScore"]),
},
methods: {
removePlayer(playerName) {
this.$store.dispatch("removePlayer", playerName);
},
newPlayerValidation() {
/* Validation variables */
let errors = [];
let newName = this.newPlayerInputText;
/* Check for empty value */
if (newName.trim() === "") {
errors.push("No player name was entered");
}
/* Prepare data of players and check for already entered ones */
let copyOfPlayerList = this.listOfPlayers;
let arrayOfPlayers = copyOfPlayerList.map((player) => player.playerName.toLowerCase());
if (arrayOfPlayers.includes(newName.toLowerCase())) {
errors.push("This player already exists");
}
/* Check amount of players */
if (arrayOfPlayers.length > 6) {
errors.push("Too many players already exists");
}
/* If everything went fine, return an empty */
return errors;
},
addNewPlayer() {
let errors = this.newPlayerValidation();
if (errors.length === 0) {
this.$store.dispatch("addNewPlayer", this.newPlayerInputText);
this.newPlayerInputText = "";
} else {
console.log(errors);
}
},
toggleNewPlayerInput() {
this.showNewPlayerInput = !this.showNewPlayerInput;
if (this.showNewPlayerInput === true) {
this.$nextTick(() => this.$refs.inputField.focus());
}
},
changeScore(playerName, score) {
let payload = {
playerName: playerName,
score: score,
};
this.$store.dispatch("changePlayerScore", payload);
for (let i = 0; i < this.listOfPlayers.length; i++) {
/* If a player received their first point */
if (this.listOfPlayers[i].score == 1 && this.listOfPlayers[i].welcomed === false) {
let payload = {
playerName: playerName,
welcomeSet: true,
};
this.$store.dispatch("changePlayerWelcomed", payload);
this.$emit("play-welcome-sound");
}
/* If a player reach match point */
if (this.listOfPlayers[i].score === this.winningScore-1) {
let payload = {
playerName: playerName,
matchPointSet: true,
};
this.$store.dispatch("changePlayerMatchPoint", payload);
this.$emit("play-match-point-sound");
}
/* If a player won */
if (this.listOfPlayers[i].score === this.winningScore) {
this.$refs.winningModal.openModal(this.listOfPlayers[i].playerName);
this.$emit("play-winning-sound");
}
}
},
openCharacterModal(playerName) {
this.$refs.characterModal.openModal(playerName);
},
getProfileSrc(profileToGet) {
console.log(this.listOfPlayers[0]);
let src = `characters/${profileToGet}`;
console.log(src);
return src;
},
toggleDisplay(event) {
if (event.target.classList[0] != "addPlayerIMG") {
if (window.innerWidth < 600) {
this.display = !this.display;
}
}
},
},
};
</script>
<style scoped>
.playerList-enter-from,
.playerList-leave-to {
opacity: 0;
}
.playerList-enter-to,
.playerList-leave-from {
opacity: 1;
}
.playerList-enter-active {
transition: all 0.5s ease-out;
}
.playerList-leave-active {
position: absolute;
transition: all 0.2s ease-in;
}
.playerList-move {
transition: 0.25s;
transition-property: transform, opacity;
}
.playerList-move.newPlayerInput {
transition: 0.2s;
transition-property: transform;
}
/* End of Animation block */
.playersWindowDiv {
display: inline-block;
width: 38vw;
color: white;
}
.playerListTitle {
display: flex;
width: 100%;
background-color: #333;
height: 5vh;
align-content: center;
align-items: center;
text-align: center;
}
.playerListTitle h1 {
text-align: center;
position: relative;
margin-left: auto;
margin-right: auto;
flex-grow: 100;
}
.playerListTitle .addPlayerIMG {
justify-self: flex-end;
margin-left: auto;
margin-right: 5px;
flex-grow: 1;
filter: invert(66%) sepia(96%) saturate(2783%) hue-rotate(1deg) brightness(105%) contrast(103%);
}
.playerListTitle .arrow {
flex-grow: 1;
}
.playerListTitle .arrowToggle {
display: none;
}
.playerList {
display: inline-block;
background-color: rgb(66, 66, 66);
min-height: 37vh;
}
.playerList .player {
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
width: 38vw;
height: 6vh;
font-size: 1.8rem;
align-content: center;
align-items: center;
}
.playerList .player p {
margin-left: 5px;
margin-right: auto;
}
.playerList .player .profilePicture {
width: 100px;
height: 50px;
background-color: red;
margin-right: 2vw;
cursor: pointer;
}
.playerList .player button {
align-content: center;
padding: 3px;
width: 50px;
height: 4vh;
margin-right: 0.6vw;
line-height: 0;
}
.playerList .player button:last-of-type {
margin-right: 3vw;
}
.playerList .player img {
margin-right: 5px;
}
.newPlayerInput {
display: flex;
width: 38vw;
height: 50px;
align-items: center;
justify-content: center;
padding: 5px 0;
background-color: rgb(107, 106, 106);
}
.newPlayerInput p {
font-size: 1.3rem;
flex-grow: 1;
text-align: center;
}
.newPlayerInput input {
height: 70%;
width: 250px;
font-size: 1.5rem;
margin: 0 5px;
text-align: center;
}
.newPlayerInput button {
margin: 0 10px;
height: 80%;
align-self: center;
}
@media only screen and (max-width: 1000px) {
.playersWindowDiv {
width: 100%;
}
.playerListTitle {
height: 10vw;
font-size: 0.8rem;
}
.playerListTitle .addPlayerIMG {
height: 9vw;
}
.playerListTitle .arrowToggle {
display: block;
max-height: 100%;
left: 0;
filter: invert(77%) sepia(8%) saturate(6%) hue-rotate(317deg) brightness(95%) contrast(87%);
}
.playerList {
width: 100%;
height: 60vw;
min-height: 190px;
overflow: scroll;
}
.playerList .player {
width: 100vw;
height: 12vw;
font-size: 1rem;
}
.playerList .player .profilePicture {
width: 60px;
height: 30px;
margin-right: 1.5vw;
}
.playerList .player button {
align-content: center;
padding: 3px;
width: 45px;
height: 10vw;
margin-right: 1vw;
line-height: 0;
}
.newPlayerInput {
width: 100%;
height: 30px;
}
.newPlayerInput input {
height: 90%;
width: 200px;
font-size: 1.2rem;
}
.newPlayerInput button {
margin: 0 10px;
height: 40px;
border-radius: 2px;
}
}
</style>
@@ -1,191 +0,0 @@
<template>
<div class="playlistHistoryDiv">
<div @click="toggleDisplay" class="playlistHistoryTitle">
<h1>Playlist history</h1>
<img v-if="display" src="keyboard_arrow_up-black-36dp.svg" alt="" />
<img v-else src="keyboard_arrow_down-black-36dp.svg" alt="" />
</div>
<transition-group
tag="div"
v-if="display"
name="playlistHistory"
class="playlistHistory"
ref="playlistHistory"
>
<div
class="track"
v-for="track in playlistHistory"
:key="track.SongNo"
@click="playSelectedTrack(track, $event)"
>
<p
v-if="checkIfHidden(track, playlistHistory)"
:class="{ activeTrack: track.CurrentlyPlaying }"
>
??? - ???
</p>
<p v-else-if="track.Game !== ''" :class="{ activeTrack: track.CurrentlyPlaying }">
{{ track.SongNo + 1 }}. {{ track.Game }} -
{{ displayTrack(track.Song) }}
</p>
<span v-if="currentlyLoadingTrack === track.SongNo" class="loadingTrack">
-- Loading --
</span>
</div>
</transition-group>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
display: true,
};
},
computed: {
...mapState(["playlistHistory", "currentTrackHidden", "currentlyLoadingTrack"]),
},
methods: {
checkIfHidden(track, playlistHistory) {
return track.SongNo === playlistHistory.length - 1 && this.currentTrackHidden;
},
displayTrack(trackName) {
/* Todo: Is this if-check necessary? */
if (trackName === undefined) {
return "";
} else {
return trackName.replace(".mp3", "");
}
},
playSelectedTrack(track, event) {
console.log(event.path);
this.$store.dispatch("setCurrentlyLoadingTrack", track.SongNo);
this.$emit("play-selected-track", track);
},
scrollToBottom() {
if (this.display) {
this.$nextTick(() => {
this.$refs.playlistHistory.$el.scrollTop = this.$refs.playlistHistory.$el.scrollHeight;
});
}
},
toggleDisplay() {
if (window.innerWidth < 600) {
this.display = !this.display;
}
},
},
created() {
if (window.innerWidth < 600) {
this.display = false;
}
},
};
</script>
<style scoped>
/* Scroll */
.playlistHistory::-webkit-scrollbar {
width: 17px;
}
.playlistHistory {
scrollbar-color: #4f5253c2;
}
.playlistHistory::-webkit-scrollbar-track {
background: #393c4179;
border-radius: 5px;
}
.playlistHistory::-webkit-scrollbar-thumb {
background-color: #6d7679c2;
border-radius: 5px;
border: 2px solid #3c434e79;
}
/* End of Scroll */
.playlistHistoryDiv {
display: inline-block;
width: 60vw;
color: white;
margin-right: 0.5vw;
}
.playlistHistoryTitle {
display: flex;
background-color: #333;
height: 5vh;
justify-content: center;
align-items: center;
}
.playlistHistoryTitle img {
display: none;
}
.playlistHistory {
background-color: rgb(66, 66, 66);
width: 60vw;
height: 37vh;
overflow-x: hidden;
overflow-y: scroll;
}
.playlistHistory .track {
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
width: 57vw;
height: auto;
min-height: 30px;
line-height: 1.5;
font-size: 1.1rem;
align-items: center;
padding: 0.3vh 0.6vh;
cursor: pointer;
}
.playlistHistory .track:nth-child(even) {
background-color: #4e4e4e;
}
.playlistHistory .activeTrack {
color: yellow;
}
.loadingTrack {
color: #ff9c00;
margin-left: 20px;
}
@media only screen and (max-width: 1000px) {
.playlistHistory::-webkit-scrollbar {
width: 5px;
}
.playlistHistoryDiv {
width: 100%;
margin: 0 0 0.3vw;
}
.playlistHistoryTitle {
height: 10vw;
font-size: 0.8rem;
}
.playlistHistoryTitle img {
display: block;
position: absolute;
max-height: 100%;
left: 0;
filter: invert(77%) sepia(8%) saturate(6%) hue-rotate(317deg) brightness(95%) contrast(87%);
}
.playlistHistory {
width: 100vw;
height: 56vw;
}
.playlistHistory .track {
width: 98vw;
font-size: 0.8rem;
padding: 1.5vw 1vw;
}
.loadingTrack {
display: none;
}
}
</style>
@@ -1,209 +0,0 @@
<template>
<!-- Round Settings, Stop after current, Don't hide title -->
<transition name="modalAni">
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
<div class="modalContainer">
<div class="modalWrapper">
<img
class="closeModalImg"
src="cancel-black-36dp.svg"
alt="closeModalIMG"
@click="closeModal"
/>
<h1>Settings</h1>
<div class="checkboxDiv">
<input
type="checkbox"
name="stopAfterCurrent"
id="stopAfterCurrent"
v-model="checkboxStopAfterCurrent"
@change="updateOptionStopAfterCurrent"
/>
<label for="stopAfterCurrent">Stop after current</label>
</div>
<div class="checkboxDiv">
<input
type="checkbox"
name="hideTitle"
id="hideTitle"
v-model="checkboxHideTitle"
@change="updateOptionHideTitle"
/>
<label for="hideTitle">Hide next track</label>
</div>
<div class="checkboxDiv">
<input
type="checkbox"
name="addPlayed"
id="addPlayed"
v-model="checkboxAddPlayed"
@change="updateOptionAddPlayed"
/>
<label for="addPlayed">Turn on played to database</label>
</div>
<div class="checkboxDiv">
<input
type="checkbox"
name="lowPlayed"
id="lowPlayed"
v-model="checkboxLowPlayed"
@change="updateOptionLowPlayed"
/>
<label for="lowPlayed">Use low played mode</label>
</div>
<div class="winningScoreDiv">
<span>Winning Score: {{ winningScore }}</span>
<button
:class="{ disabled: roundStarted }"
:disabled="roundStarted"
@click="changeScore(-1)"
>
-1
</button>
<button
:class="{ disabled: roundStarted }"
:disabled="roundStarted"
@click="changeScore(1)"
>
+1
</button>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
show: false,
checkboxStopAfterCurrent: false,
checkboxHideTitle: false,
checkboxAddPlayed: false,
checkboxLowPlayed: false,
};
},
computed: {
...mapState(["winningScore", "roundStarted", "stopAfterCurrent", "hideNextTrack", "addPlayed", "lowPlayed"]),
},
methods: {
closeModal() {
this.show = false;
},
openModal() {
this.show = true;
},
checkIfClickShouldCloseModal(event) {
if (event.target.classList[0] === "modal") {
this.closeModal();
}
},
updateOptionHideTitle() {
if (this.checkboxHideTitle) {
this.$store.dispatch("updateHideNextTitle", true);
} else {
this.$store.dispatch("updateHideNextTitle", false);
}
},
updateOptionAddPlayed() {
if (this.checkboxAddPlayed) {
this.$store.dispatch("updateAddPlayed", true);
} else {
this.$store.dispatch("updateAddPlayed", false);
}
},
updateOptionLowPlayed() {
if (this.checkboxLowPlayed) {
this.$store.dispatch("updateLowPlayed", true);
} else {
this.$store.dispatch("updateLowPlayed", false);
}
},
updateOptionStopAfterCurrent() {
if (this.checkboxStopAfterCurrent) {
this.$store.dispatch("updateStopAfterCurrent", true);
} else {
this.$store.dispatch("updateStopAfterCurrent", false);
}
},
changeScore(score) {
if (score < 0) {
this.$store.dispatch("setWinningScore", -1);
} else {
this.$store.dispatch("setWinningScore", 1);
}
},
},
mounted() {
this.checkboxStopAfterCurrent = this.stopAfterCurrent;
this.checkboxHideTitle = this.hideNextTrack;
this.checkboxAddPlayed = this.addPlayed;
this.checkboxLowPlayed = this.lowPlayed;
},
};
</script>
<style scoped>
.winningScoreDiv {
width: 100%;
display: inline-flex;
align-items: center;
flex-wrap: nowrap;
margin-top: 30px;
}
.checkboxDiv {
margin: 5px;
width: 100%;
}
.checkboxDiv:first-of-type {
margin-top: 20px;
}
.checkboxDiv input {
transform: scale(1.3);
}
.checkboxDiv label {
margin-left: 10px;
font-size: 1.5rem;
}
.winningScoreDiv span {
font-size: 1.7rem;
margin-right: 10px;
width: 245px;
}
.winningScoreDiv button {
display: flex;
width: 50px;
height: 33px;
background: rgb(16, 99, 194);
color: rgb(235, 235, 235);
border-color: rgb(161, 161, 161);
justify-content: center;
align-items: center;
line-height: 0;
font-size: 1.5rem;
margin: 3px;
}
.disabled {
opacity: 50%;
}
@media only screen and (max-width: 1000px) {
.checkboxDiv label {
font-size: 1.3rem;
}
.winningScoreDiv span {
font-size: 1.3rem;
margin-right: 10px;
width: 245px;
}
}
</style>
@@ -1,68 +0,0 @@
<template>
<transition name="modalAni">
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
<div class="modalContainer">
<div class="modalWrapper">
<img
class="closeModalImg"
src="cancel-black-36dp.svg"
alt="closeModalIMG"
@click="closeModal"
/>
<h1>Statistics</h1>
<p>Total amount of games in the playlist: {{ howManyGames }}</p>
</div>
</div>
</div>
</transition>
</template>
<script>
import { mapState } from "vuex";
export default {
data() {
return {
show: false,
};
},
computed: {
...mapState(["howManyGames"]),
},
methods: {
closeModal() {
this.show = false;
},
openModal() {
this.show = true;
},
checkIfClickShouldCloseModal(event) {
if (event.target.classList[0] === "modal") {
this.closeModal();
}
},
},
};
</script>
<style scoped>
p {
font-size: 1.2rem;
width: 100%;
margin-top: 30px;
}
.descriptionText {
margin-top: 35px;
}
.creditText {
margin-top: 30px;
}
.creditText:nth-child(5) {
margin-top: 0px;
}
.patchNotesText {
margin-top: 20px;
}
</style>
@@ -1,50 +0,0 @@
<template>
<transition name="modalAni">
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
<div class="modalContainer">
<div class="modalWrapper">
<img
class="closeModalImg"
src="cancel-black-36dp.svg"
alt="closeModalIMG"
@click="closeModal"
/>
<h1>{{ playerName }} won!!</h1>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
data() {
return {
show: false,
playerName: "",
};
},
methods: {
closeModal() {
this.show = false;
},
showPeter() {
let randomNumber = Math.floor(Math.random() * 10);
console.log(randomNumber);
console.log("Test");
return randomNumber < 2;
},
openModal(playerName) {
this.playerName = playerName;
this.show = true;
},
checkIfClickShouldCloseModal(event) {
if (event.target.classList[0] === "modal") {
this.closeModal();
}
},
},
};
</script>
<style scoped></style>
@@ -1,441 +0,0 @@
<template>
<footer>
<div class="audioAndButtonsDiv">
<div class="audioButtonsDiv">
<button
class="bigButton"
:class="{ disabled: disableButtons }"
:disabled="disableButtons"
@click="randomizeTrack()"
>
<p v-if="!disableButtons"><u>R</u>andomize new track</p>
<p v-else>Loading...</p>
</button>
<button class="bigButton" @click="showAnswer()"><u>S</u>how answer</button>
</div>
<div class="audioDiv">
<audio
controls
class="audioPlayer"
:src="currentTrackSrcFile"
ref="audioPlayer"
:onended="trackHasEnded"
></audio>
</div>
</div>
</footer>
</template>
<script>
import { mapState } from "vuex";
import arne from "../../arne.js";
export default {
data() {
return {
currentTrackSrcFile: "",
disableButtons: false,
loadingBar: "",
};
},
computed: {
...mapState([
"stopAfterCurrent",
"localPlaylist",
"hideNextTrack",
"roundStarted",
"specialTrackIsPlaying",
"addPlayed",
"lowPlayed",
]),
},
methods: {
async randomizeTrack() {
console.log("Randomizing track");
/* Prevents anyone from changing the winning score after the round has started */
if (this.roundStarted === false) {
this.$store.dispatch("setRoundStarted", true);
}
let trackAddedToQue = false;
this.disableButtons = true;
let copyOfPlaylist = this.localPlaylist;
setTimeout(() => this.isHidden = false, 2000);
//If the local playlist of MP3s is empty, wait for a new track to load
if (copyOfPlaylist.length === 0) {
console.log("No track preloaded, loading a new one");
if (this.lowPlayed) {
await this.APIgetRandomizedLowTrack();
} else {
await this.APIgetRandomizedTrack();
}
if (this.addPlayed) {
await this.APIaddPlayed();
}
await this.APIaddToQue();
trackAddedToQue = true;
}
//Update the source file for the media player (binded property to Audio tag in the template)
this.currentTrackSrcFile = window.URL.createObjectURL(copyOfPlaylist[0]);
if (!trackAddedToQue) {
if (this.addPlayed) {
await this.APIaddPlayed();
}
await this.APIaddToQue();
}
await this.APIgetPlaylistHistory();
this.$refs.audioPlayer.play();
this.$emit("scroll-to-bottom-playlist-history");
copyOfPlaylist.shift();
if (this.hideNextTrack) {
this.$store.dispatch("setCurrentTrackHidden", true);
} else {
this.$store.dispatch("setCurrentTrackHidden", false);
}
await this.APIgetInfoAboutCurrentTrack();
await this.randomizeTrackInAdvance();
this.disableButtons = false;
},
async randomizeTrackInAdvance() {
console.log("Randomizing track in advance");
try {
if (this.lowPlayed) {
await this.APIgetRandomizedLowTrack();
} else {
await this.APIgetRandomizedTrack();
}
} catch (error) {
console.log(error);
console.log("Error: A track couldn't be loaded in advance");
}
return new Promise((resolve) => {
resolve();
});
},
async playSpecificTrack(track) {
await this.APIplaySpecificTrack(track.SongNo);
this.$store.dispatch("setCurrentlyLoadingTrack", "");
await this.APIgetPlaylistHistory();
this.$store.dispatch("setCurrentTrackHidden", false);
await this.APIgetInfoAboutCurrentTrack();
},
showAnswer() {
this.$store.dispatch("setCurrentTrackHidden", false);
},
startSoundTest() {
this.$refs.audioPlayer.pause();
this.currentTrackSrcFile = "sounds/check.mp3";
this.$store.dispatch("setSpecialTrackIsPlaying", true);
this.$nextTick(() => {
this.$refs.audioPlayer.currentTime = 0;
this.$refs.audioPlayer.play();
});
},
playWelcomeSound() {
let randomNumber = Math.floor(Math.random() * 10);
this.$refs.audioPlayer.pause();
if (randomNumber === 0) {
this.currentTrackSrcFile = "sounds/sound1.mp3";
} else if (randomNumber < 5) {
this.currentTrackSrcFile = "sounds/intro_short.mp3";
} else {
this.currentTrackSrcFile = "sounds/intro_long.mp3";
}
this.$store.dispatch("setSpecialTrackIsPlaying", true);
this.$nextTick(() => {
this.$refs.audioPlayer.currentTime = 0;
this.$refs.audioPlayer.play();
});
},
playMatchSound() {
this.$refs.audioPlayer.pause();
this.currentTrackSrcFile = "sounds/sound0.mp3";
this.$store.dispatch("setSpecialTrackIsPlaying", true);
this.$nextTick(() => {
this.$refs.audioPlayer.currentTime = 0;
this.$refs.audioPlayer.play();
});
},
playWinningSound() {
this.$refs.audioPlayer.pause();
this.currentTrackSrcFile = "sounds/winning.mp3";
this.$store.dispatch("setSpecialTrackIsPlaying", true);
this.$nextTick(() => {
this.$refs.audioPlayer.currentTime = 0;
this.$refs.audioPlayer.play();
});
},
APIgetRandomizedTrack() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/rand`,
responseType: "blob",
onDownloadProgress: (progressEvent) => {
let percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
if (percentCompleted % 10 === 0) {
//Debug for slow downloading
//console.log(percentCompleted);
}
},
})
.then((response) => {
let mp3 = new Blob([response.data], { type: "audio/mp3" });
this.$store.dispatch("updateLocalPlaylist", mp3);
this.$nextTick(() => {
resolve();
});
})
.catch(function(error) {
console.log("An error happened when fetching the track");
console.log(error);
reject();
});
});
},
APIgetRandomizedLowTrack() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/rand/low`,
responseType: "blob",
onDownloadProgress: (progressEvent) => {
let percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
if (percentCompleted % 10 === 0) {
//Debug for slow downloading
//console.log(percentCompleted);
}
},
})
.then((response) => {
let mp3 = new Blob([response.data], { type: "audio/mp3" });
this.$store.dispatch("updateLocalPlaylist", mp3);
this.$nextTick(() => {
resolve();
});
})
.catch(function(error) {
console.log("An error happened when fetching the track");
console.log(error);
reject();
});
});
},
APIaddPlayed() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/addPlayed`,
})
.then(() => {
resolve(false);
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
APIaddToQue() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/addQue`,
})
.then(() => {
resolve(false);
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
APIgetInfoAboutCurrentTrack() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/info`,
})
.then((response) => {
let gameInfoObject = {
game: response.data.Game,
track: response.data.Song.replace(".mp3", ""),
songNo: response.data.SongNo,
};
this.$store.dispatch("setCurrentGame", gameInfoObject.game);
this.$store.dispatch("setCurrentTrack", gameInfoObject.track);
resolve(false);
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
APIgetPlaylistHistory() {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music/list`,
})
.then((response) => {
let tracklistArray = response.data;
this.$store.dispatch("updatePlaylistHistory", tracklistArray);
resolve();
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
APIplaySpecificTrack(trackNumber) {
return new Promise((resolve, reject) => {
this.axios({
method: "get",
url: `${arne.hostname}/music?song=${trackNumber}`,
responseType: "blob",
})
.then((response) => {
let mp3 = new Blob([response.data], { type: "audio/mp3" });
this.currentTrackSrcFile = window.URL.createObjectURL(mp3);
this.$nextTick(() => {
this.$refs.audioPlayer.play();
resolve();
});
})
.catch(function(error) {
console.log(error);
reject();
});
});
},
trackHasEnded() {
if (this.specialTrackIsPlaying) {
this.$store.dispatch("setSpecialTrackIsPlaying", false);
return 0;
}
if (!this.stopAfterCurrent) {
this.randomizeTrack();
}
},
},
async mounted() {
await this.APIgetPlaylistHistory();
},
};
</script>
<style scoped>
footer {
display: flex;
position: absolute;
bottom: 0;
justify-content: flex-end;
width: 100%;
}
.audioAndButtonsDiv {
display: flex;
flex-wrap: wrap;
width: 80vw;
margin-right: 1vh;
}
.audioButtonsDiv {
display: flex;
flex-direction: row-reverse;
width: 100%;
margin-bottom: 2vh;
align-items: center;
}
.audioButtonsDiv button:nth-child(1) {
margin-right: 3vw;
width: 200px;
}
.audioButtonsDiv button:nth-child(2) {
margin-right: 25vw;
width: 220px;
}
.audioButtonsDiv .bigButton {
height: 7vh;
}
.disabled {
opacity: 0.5;
}
.audioDiv {
width: 100%;
}
.audioDiv audio {
width: 100%;
font-size: 2rem;
}
audio::-webkit-media-controls-panel {
background-color: rgb(185, 185, 185);
}
audio::-webkit-media-controls-current-time-display {
color: rgb(0, 0, 0);
font-size: 4em;
}
audio::-webkit-media-controls-time-remaining-display {
color: rgb(0, 0, 0);
font-size: 4em;
}
@media only screen and (max-width: 1000px) {
footer {
position: static;
justify-content: center;
}
.audioAndButtonsDiv {
width: 100vw;
margin: 0;
}
.audioButtonsDiv {
margin-bottom: 2vw;
font-size: 1rem;
}
.audioButtonsDiv button:nth-child(1) {
margin: 0;
width: 50vw;
border-radius: 2px;
}
.audioButtonsDiv button:nth-child(2) {
margin: 0;
width: 50vw;
border-radius: 2px;
}
.audioButtonsDiv .bigButton {
height: 22vw;
}
.disabled {
opacity: 0.5;
}
.audioDiv {
width: 100%;
}
.audioDiv audio {
font-size: 1rem;
}
audio::-webkit-media-controls-panel {
background-color: rgb(185, 185, 185);
border-radius: 2px;
}
audio::-webkit-media-controls-current-time-display {
color: rgb(0, 0, 0);
font-size: 2em;
}
audio::-webkit-media-controls-time-remaining-display {
color: rgb(0, 0, 0);
font-size: 2em;
}
}
</style>
@@ -1,139 +0,0 @@
<template>
<nav>
<p class="title">Music Player Randomizer</p>
<div class="navMenuWrapper">
<button class="settingsButton" @click="showSettingsModal">
Settings
</button>
<button class="statisticsButton" @click="showStatisticsModal">
Statistics
</button>
<button class="aboutButton" @click="showAboutModal">About</button>
</div>
<p v-if="!$parent.displayWhenDesktop" class="mobileMenuButton" @click="showMobileMenu">
Menu
</p>
<settingsModal ref="settingsModal"></settingsModal>
<statisticsModal ref="statisticsModal"></statisticsModal>
<aboutModal ref="aboutModal"></aboutModal>
<mobileMenu
ref="mobileMenu"
@start-sound-test="startSoundTest"
@show-settings-modal="showSettingsModal"
@show-statistics-modal="showStatisticsModal"
@show-about-modal="showAboutModal"
></mobileMenu>
</nav>
</template>
<script>
import aboutModal from "../items/aboutModal.vue";
import statisticsModal from "../items/statisticsModal.vue";
import settingsModal from "../items/settingsModal.vue";
import mobileMenu from "../items/mobileMenu.vue";
export default {
components: {
aboutModal,
settingsModal,
statisticsModal,
mobileMenu,
},
methods: {
playSelectedTrack(track) {
this.$refs.theFooter.playSpecificTrack(track);
},
scrollToBottomPlaylistHistory() {
this.$refs.playlistHistory.scrollToBottom();
},
showAboutModal() {
this.$refs.aboutModal.openModal();
},
showSettingsModal() {
this.$refs.settingsModal.openModal();
},
showStatisticsModal() {
this.$refs.statisticsModal.openModal();
},
showMobileMenu() {
this.$refs.mobileMenu.openMobileMenu();
this.showMobileMenuContent();
},
showMobileMenuContent() {
setTimeout(() => {
this.$refs.mobileMenu.openMobileMenuContent();
}, 20);
},
startSoundTest() {
this.$emit("start-sound-test");
},
},
};
</script>
<style scoped>
nav {
background: #333;
display: flex;
align-content: center;
align-items: center;
width: 100%;
height: 5vh;
}
.title {
color: #fff;
font-size: 2rem;
margin-left: 2vw;
font-style: normal;
}
.navMenuWrapper {
margin-left: auto;
height: 5vh;
}
.navMenuWrapper button {
background: #333;
color: #ff9c00;
font-size: 1.3rem;
outline: none;
cursor: pointer;
border: none;
border-radius: 3px;
height: 100%;
padding: 0 20px;
}
.navMenuWrapper button:hover {
background-color: #555;
}
/* Mobile */
@media only screen and (max-width: 1000px) {
nav {
justify-content: center;
height: 10vw;
}
.title {
font-size: 1.2rem;
margin: 0;
padding: 0;
width: 100%;
margin-left: 5px;
/* text-align: ; */
}
.navMenuWrapper {
display: none;
}
.mobileMenuButton {
color: white;
background: grey;
padding: 6px;
border-top-left-radius: 7px;
border-bottom-left-radius: 7px;
}
}
</style>
@@ -1,103 +0,0 @@
<template>
<div class="theMainDiv">
<div class="mainFirstRow">
<currently-playing></currently-playing>
<inspiration-window></inspiration-window>
</div>
<div class="mainSecondRow">
<playlist-history
ref="playlistHistory"
@play-selected-track="playSelectedTrack"
></playlist-history>
<players-window
@play-welcome-sound="playWelcomeSound"
@play-match-point-sound="playMatchSound"
@play-winning-sound="playWinningSound"
></players-window>
</div>
<extra-buttons
v-if="$parent.displayWhenDesktop"
@start-sound-test="startSoundTest"
></extra-buttons>
<the-footer
ref="theFooter"
@scroll-to-bottom-playlist-history="scrollToBottomPlaylistHistory"
></the-footer>
<!-- <the-footer ref="theFooter"></the-footer> -->
</div>
</template>
<script>
import currentlyPlaying from "../items/CurrentlyPlaying.vue";
import inspirationWindow from "../items/InspirationWindow.vue";
import playersWindow from "../items/playersWindow.vue";
import playlistHistory from "../items/playlistHistory.vue";
import extraButtons from "../items/extraButtons.vue";
import theFooter from "./TheFooter.vue";
export default {
components: {
currentlyPlaying,
playersWindow,
playlistHistory,
inspirationWindow,
extraButtons,
theFooter,
},
methods: {
playSelectedTrack(track) {
this.$refs.theFooter.playSpecificTrack(track);
},
scrollToBottomPlaylistHistory() {
this.$refs.playlistHistory.scrollToBottom();
},
startSoundTest() {
this.$refs.theFooter.startSoundTest();
},
playWelcomeSound() {
this.$refs.theFooter.playWelcomeSound();
},
playMatchSound() {
this.$refs.theFooter.playMatchSound();
},
playWinningSound() {
this.$refs.theFooter.playWinningSound();
},
},
};
</script>
<style scoped>
.theMainDiv {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.mainFirstRow {
display: flex;
flex-wrap: nowrap;
width: 100%;
margin: 0.5vw;
}
.mainSecondRow {
display: flex;
flex-wrap: nowrap;
width: 100%;
margin: 0 0.5vw;
}
@media only screen and (max-width: 1000px) {
.mainFirstRow {
margin: 0;
margin-top: 0.5vw;
}
.mainSecondRow {
display: flex;
flex-wrap: wrap;
width: 100%;
margin: 0;
margin-top: 0.5vw;
}
}
</style>
-261
View File
@@ -1,261 +0,0 @@
import { createApp } from "vue";
import { createStore } from "vuex";
import App from "./App.vue";
import axios from "axios";
import VueAxios from "vue-axios";
const store = createStore({
state() {
return {
currentGame: "",
currentTrack: ``,
currentTrackHidden: false,
currentlyLoadingTrack: "",
specialTrackIsPlaying: false,
someoneHasWon: false,
winningScore: 20,
roundStarted: false,
reloadGamesList: false,
listOfPlayers: [],
localPlaylist: [],
playlistHistory: [
{
SongNo: "",
Game: "",
Song: "",
},
],
/* Stats */
howManyGames: 0,
/* Options */
stopAfterCurrent: true,
hideNextTrack: true,
addPlayed: true,
lowPlayed: false,
};
},
mutations: {
setCurrentGame(state, payload) {
state.currentGame = payload;
},
setCurrentTrack(state, payload) {
state.currentTrack = payload;
},
setCurrentTrackHidden(state, payload) {
state.currentTrackHidden = payload;
},
setCurrentlyLoadingTrack(state, payload) {
state.currentlyLoadingTrack = payload;
},
setSpecialTrackIsPlaying(state, payload) {
state.specialTrackIsPlaying = payload;
},
setSomeoneHasWon(state, payload) {
state.someoneHasWon = payload;
},
setWinningScore(state, payload) {
state.winningScore += payload;
},
setRoundStarted(state, payload) {
state.roundStarted = payload;
},
updateListOfPlayers(state, payload) {
state.listOfPlayers = payload;
},
changePlayerScore(state, payload) {
state.listOfPlayers[payload.indexOfPlayer].score += payload.scoreToChange;
},
changePlayerWelcomed(state, payload) {
state.listOfPlayers[payload.indexOfPlayer].welcomed = payload.playerWelcomeTrueOrFalse;
},
changePlayerMatchPoint(state, payload) {
state.listOfPlayers[payload.indexOfPlayer].matchPoint = payload.playerMatchPointTrueOrFalse;
},
changePlayerProfile(state, payload) {
state.listOfPlayers[payload.indexOfPlayer].profile = payload.profileSrc;
},
resetPlayerScore(state, payload) {
state.listOfPlayers = payload;
},
resetPlayerWelcomed(state, payload) {
state.listOfPlayers = payload;
},
resetPlayerMatchPoint(state, payload) {
state.listOfPlayers = payload;
},
updateLocalPlaylist(state, payload) {
state.localPlaylist.push(payload);
},
updatePlaylistHistory(state, payload) {
state.playlistHistory = payload;
},
updateHowManyGames(state, payload) {
state.howManyGames = payload;
},
reloadGamesList(state, payload) {
state.reloadGamesList = payload;
},
updateStopAfterCurrent(state, payload) {
state.stopAfterCurrent = payload;
},
updateHideNextTitle(state, payload) {
state.hideNextTrack = payload;
},
updateAddPlayed(state, payload) {
state.addPlayed = payload;
},
updateLowPlayed(state, payload) {
state.lowPlayed = payload;
},
},
actions: {
setCurrentGame(context, payload) {
context.commit("setCurrentGame", payload);
},
setCurrentTrack(context, payload) {
context.commit("setCurrentTrack", payload);
},
setCurrentTrackHidden(context, payload) {
context.commit("setCurrentTrackHidden", payload);
},
setCurrentlyLoadingTrack(context, payload) {
context.commit("setCurrentlyLoadingTrack", payload);
},
setSpecialTrackIsPlaying(context, payload) {
context.commit("setSpecialTrackIsPlaying", payload);
},
setSomeoneHasWon(context, payload) {
context.commit("someoneHasWon", payload);
},
setWinningScore(context, payload) {
context.commit("setWinningScore", payload);
},
setRoundStarted(context, payload) {
context.commit("setRoundStarted", payload);
},
removePlayer(context, payload) {
let newPlayerList = this.state.listOfPlayers.filter(
(player) => player.playerName != payload
);
context.commit("updateListOfPlayers", newPlayerList);
},
addNewPlayer(context, payload) {
let newPlayerList = this.state.listOfPlayers;
let newPlayer = {
playerName: payload,
score: 0,
welcomed: false,
matchPoint: false,
profile: "characters/noCharacter.png",
};
newPlayerList.push(newPlayer);
context.commit("updateListOfPlayers", newPlayerList);
},
changePlayerScore(context, payload) {
let copyOfPlayerList = this.state.listOfPlayers;
let indexOfPlayer = copyOfPlayerList.findIndex(
(player) => player.playerName === payload.playerName
);
let scoreToChange = payload.score;
context.commit("changePlayerScore", {
indexOfPlayer,
scoreToChange,
});
},
changePlayerWelcomed(context, payload) {
let copyOfPlayerList = this.state.listOfPlayers;
let indexOfPlayer = copyOfPlayerList.findIndex(
(player) => player.playerName === payload.playerName
);
let playerWelcomeTrueOrFalse = payload.welcomeSet;
context.commit("changePlayerWelcomed", {
indexOfPlayer,
playerWelcomeTrueOrFalse,
});
},
changePlayerMatchPoint(context, payload) {
let copyOfPlayerList = this.state.listOfPlayers;
let indexOfPlayer = copyOfPlayerList.findIndex(
(player) => player.playerName === payload.playerName
);
let playerMatchPointTrueOrFalse = payload.matchPointSet;
context.commit("changePlayerMatchPoint", {
indexOfPlayer,
playerMatchPointTrueOrFalse,
});
},
changePlayerProfile(context, payload) {
let copyOfPlayerList = this.state.listOfPlayers;
let indexOfPlayer = copyOfPlayerList.findIndex(
(player) => player.playerName === payload.playerName
);
let profileSrc = payload.profile;
context.commit("changePlayerProfile", {
indexOfPlayer,
profileSrc,
});
},
resetPlayerScore(context) {
/* The JSON.parse and JSON.stringify parts are used to make a copy that doesn't affect the original */
let copyOfPlayerList = JSON.parse(JSON.stringify(this.state.listOfPlayers));
for (let i = 0; i < copyOfPlayerList.length; i++) {
copyOfPlayerList[i].score = 0;
}
context.commit("resetPlayerScore", copyOfPlayerList);
},
resetPlayerWelcomed(context) {
/* The JSON.parse and JSON.stringify parts are used to make a copy that doesn't affect the original */
let copyOfPlayerList = JSON.parse(JSON.stringify(this.state.listOfPlayers));
for (let i = 0; i < copyOfPlayerList.length; i++) {
copyOfPlayerList[i].welcomed = false;
}
context.commit("resetPlayerWelcomed", copyOfPlayerList);
},
resetPlayerMatchPoint(context) {
/* The JSON.parse and JSON.stringify parts are used to make a copy that doesn't affect the original */
let copyOfPlayerList = JSON.parse(JSON.stringify(this.state.listOfPlayers));
for (let i = 0; i < copyOfPlayerList.length; i++) {
copyOfPlayerList[i].matchPoint = false;
}
context.commit("resetPlayerMatchPoint", copyOfPlayerList);
},
updateLocalPlaylist(context, payload) {
context.commit("updateLocalPlaylist", payload);
},
updatePlaylistHistory(context, payload) {
context.commit("updatePlaylistHistory", payload);
},
updateHowManyGames(context, payload) {
context.commit("updateHowManyGames", payload);
},
reloadGamesList(context, payload) {
context.commit("reloadGamesList", payload);
},
updateStopAfterCurrent(context, payload) {
context.commit("updateStopAfterCurrent", payload);
},
updateHideNextTitle(context, payload) {
context.commit("updateHideNextTitle", payload);
},
updateAddPlayed(context, payload) {
context.commit("updateAddPlayed", payload);
},
updateLowPlayed(context, payload) {
context.commit("updateLowPlayed", payload);
},
},
getters: {
/* Empty ATM */
/* getListOfPlayers: (state) => {
return state.listOfPlayers;
}, */
},
});
const app = createApp(App);
app.use(store);
app.use(VueAxios, axios);
app.mount("#app");
+19 -62
View File
@@ -2,84 +2,41 @@ package main
import ( import (
"context" "context"
"music-server/internal/logging"
"music-server/internal/server"
"net/http"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"music-server/internal/logging"
"music-server/internal/server"
"go.uber.org/zap" "go.uber.org/zap"
) )
// const shutdownTimeout = 5 * time.Second
// @Title Swagger Example API
// @version 0.5
// @description This is a sample server Petstore server.
// @termsOfService http://swagger.io/terms/
//
// @contact.name Sebastian Olsson
// @contact.email zarnor91@gmail.com
//
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
func main() { func main() {
/*f, perr := os.Create("cpu.pprof")
if perr != nil {
log.Fatal(perr)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()*/
appServer := server.NewServerInstance()
httpServer := appServer.HTTPServer()
// Create a done channel to signal when the shutdown is complete
done := make(chan bool, 1)
// Run graceful shutdown in a separate goroutine
go gracefulShutdown(appServer, httpServer, done)
logging.GetLogger().Info("Server starting", zap.String("address", httpServer.Addr))
err := httpServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
logging.GetLogger().Fatal("HTTP server error", zap.String("error", err.Error()))
}
// Wait for the graceful shutdown to complete
<-done
logging.GetLogger().Info("Graceful shutdown complete")
}
func gracefulShutdown(appServer *server.Server, httpServer *http.Server, done chan bool) {
// Create context that listens for the interrupt signal from the OS.
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop() defer stop()
// Listen for the interrupt signal. s := server.NewServerInstance()
<-ctx.Done()
logging.GetLogger().Info("Shutting down gracefully, press Ctrl+C again to force") serveErr := make(chan error, 1)
go func() { serveErr <- s.ListenAndServe() }()
// Close database connection select {
if appServer != nil && appServer.DB() != nil { case err := <-serveErr:
appServer.DB().Close() if err != nil {
logging.GetLogger().Fatal("HTTP server error", zap.Error(err))
}
case <-ctx.Done():
stop()
logging.GetLogger().Info("Shutting down gracefully, press Ctrl+C again to force")
} }
// The context is used to inform the server it has 5 seconds to finish shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
if err := httpServer.Shutdown(ctx); err != nil { if err := s.Shutdown(shutdownCtx); err != nil {
logging.GetLogger().Error("Server forced to shutdown with error", zap.String("error", err.Error())) logging.GetLogger().Error("Server forced to shutdown with error", zap.Error(err))
} }
logging.GetLogger().Info("Graceful shutdown complete")
logging.GetLogger().Info("Server exiting")
// Notify the main goroutine that the shutdown is complete
done <- true
} }
-3
View File
@@ -4,6 +4,3 @@ import "embed"
//go:embed "assets" //go:embed "assets"
var Assets embed.FS var Assets embed.FS
//go:embed "swagger"
var Swagger embed.FS
+38 -58
View File
@@ -1,77 +1,57 @@
package web package web
import ( import (
"log"
"music-server/internal/backend"
"net/http" "net/http"
"strings" "strings"
"music-server/internal/backend"
"music-server/internal/logging"
"go.uber.org/zap"
) )
var soundtracks_added []string // NewSearchHandler returns a handler that renders the soundtracks matching the posted search term.
func NewSearchHandler(cache *backend.SearchCache) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
func FindSoundtrackWebHandler(w http.ResponseWriter, r *http.Request) { soundtracks, err := cache.Get(r.Context())
err := r.ParseForm() if err != nil {
if err != nil { logging.GetLogger().Error("Failed to load search data", zap.Error(err))
http.Error(w, "Bad Request", http.StatusBadRequest) http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return return
} }
search_term := r.FormValue("search_term") results := search(r.FormValue("search_term"), soundtracks)
search(search_term) if err := FoundSoundtracks(results).Render(r.Context(), w); err != nil {
results := soundtracks_added logging.GetLogger().Error("Failed to render search results", zap.Error(err))
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
component := FoundSoundtracks(results) }
err = component.Render(r.Context(), w) })
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
log.Fatalf("Error rendering in FindSoundtrackWebHandler: %e", err)
}
} }
func search(searchText string) { // search returns the soundtracks matching searchText: exact matches first, then matches
soundtracks_added = nil // ignoring spaces and special characters, then matches with the letters in order.
soundtrackData := backend.GetSearchCache() func search(searchText string, soundtrackData []backend.SoundtrackData) []string {
results := searchWithData(searchText, soundtrackData)
soundtracks_added = results
}
// searchWithData is a testable version of search that takes soundtrack data as a parameter
func searchWithData(searchText string, soundtrackData []backend.SoundtrackData) []string {
var results []string var results []string
seen := make(map[string]bool) seen := make(map[string]bool)
add := func(match func(backend.SoundtrackData) bool) {
// Precompute cleaned and lowercase search terms once for _, data := range soundtrackData {
cleanedSearchTerm := strings.ToLower(searchText) if !seen[data.Original] && match(data) {
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, " ", "") results = append(results, data.Original)
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "é", "e") seen[data.Original] = true
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "+", "plus") }
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "&", "and")
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "'n", "and")
lowercaseSearchText := strings.ToLower(searchText)
// Pass 1: Exact match (case-insensitive contains)
for _, data := range soundtrackData {
if !seen[data.Original] && (searchText == "" || strings.Contains(data.Lowercase, lowercaseSearchText)) {
results = append(results, data.Original)
seen[data.Original] = true
} }
} }
// Pass 2: Contains match with cleaned terms lowercase := strings.ToLower(searchText)
for _, data := range soundtrackData { cleaned := backend.CleanSearchTerm(searchText)
if !seen[data.Original] && (cleanedSearchTerm == "" || strings.Contains(data.Cleaned, cleanedSearchTerm)) {
results = append(results, data.Original)
seen[data.Original] = true
}
}
// Pass 3: Regex match with cleaned terms
for _, data := range soundtrackData {
if !seen[data.Original] && backend.MatchRegexCached(cleanedSearchTerm, data.Cleaned) {
results = append(results, data.Original)
seen[data.Original] = true
}
}
add(func(d backend.SoundtrackData) bool { return strings.Contains(d.Lowercase, lowercase) })
add(func(d backend.SoundtrackData) bool { return strings.Contains(d.Cleaned, cleaned) })
add(func(d backend.SoundtrackData) bool { return backend.MatchesSubsequence(cleaned, d.Cleaned) })
return results return results
} }
+77 -170
View File
@@ -1,193 +1,100 @@
package web package web
import ( import (
"music-server/internal/backend" "context"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"strings" "strings"
"testing" "testing"
"music-server/internal/backend"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// TestCleanTerm tests that search terms are cleaned correctly var soundtracks = []string{
func TestCleanTerm(t *testing.T) { "Call of Duty: Modern Warfare",
tests := []struct { "Final Fantasy VII",
input string "Joe & Mac",
expected string "Kingdom Hearts",
}{ "Lemmings (SNES + Mega Drive)",
// CleanSearchTermForMatch removes spaces, replaces specific chars, and lowercases "MG",
{"MGS4", "mgs4"}, "Metal Gear Solid 4 - Guns of the Patriots",
{"RE2", "re2"}, "Pokémon HeartGold & SoulSilver",
{"FFVII", "ffvii"}, "Resident Evil 2 (Remake)",
{"Ys", "ys"}, "Ys III - Wanderers from Ys (+The Oath in Felghana)",
{" test ", "test"},
{"Test 123", "test123"},
{"A+B", "aplusb"},
{"é", "e"},
// Note: CleanSearchTermForMatch does NOT remove apostrophes, it only replaces "'n" with "and"
{"It's", "it's"},
// But it does replace "'n" with "and"
{"I'n", "iand"},
{"", ""},
}
for _, tt := range tests {
result := backend.CleanSearchTermForMatch(tt.input)
if result != tt.expected {
t.Errorf("CleanSearchTermForMatch(%q) = %q, want %q", tt.input, result, tt.expected)
}
}
} }
// searchWithDataHelper is a helper to create backend.SoundtrackData from original names func newCache(names []string) *backend.SearchCache {
func searchWithDataHelper(originals []string) []backend.SoundtrackData { return backend.NewSearchCache(func(context.Context) ([]string, error) { return names, nil })
data := make([]backend.SoundtrackData, len(originals)) }
for i, orig := range originals {
data[i] = backend.SoundtrackData{ func searchData(t *testing.T) []backend.SoundtrackData {
Original: orig, data, err := newCache(soundtracks).Get(context.Background())
Lowercase: strings.ToLower(orig), require.NoError(t, err)
Cleaned: backend.CleanSearchTermForMatch(orig),
}
}
return data return data
} }
// TestNormalSearch tests the normal search function func TestSearch(t *testing.T) {
func TestNormalSearch(t *testing.T) { data := searchData(t)
// Set up comprehensive test data with all user-provided soundtracks
testData := searchWithDataHelper([]string{
"A Bird Story",
"Actraiser",
"Actraiser 2",
"Bangai-O Spirits",
"Ciconia When they Cry",
"Civilization IV",
"Civilization VI",
"Clair Obscur - Expedition 33",
"Club House Games (42 All-Time Classics)",
"Final Fantasy Dissidia 012 Duodecim (Sista Fantasin Meningsskiljaktigheter 012 Tolv)",
"Final Fantasy VII Crisis Core",
"Final Fantasy VII Rebirth",
"Final Fantasy VII Remake",
"Final Fantasy VII",
"Joe & Mac",
"Judgment",
"Kingdom Hearts",
"Kingdom Hearts Birth By Sleep",
"Kingdom Hearts II",
"Kingdom Hearts III",
"Lemmings (SNES + Mega Drive)",
"Lemmings 3D",
"Mega Man X4",
"Metal Gear Rising - Revengeance",
"Metal Gear Solid (+Twin Snakes)",
"Metal Gear Solid 2 - Sons of Liberty",
"Metal Gear Solid 3 - Snake Eater",
"Metal Gear Solid 4 - Guns of the Patriots",
"Metroid Prime",
"Octopath Traveler",
"Octopath Traveler 2",
"Ori and the Blind Forest",
"Ori and the Will of the Wisps",
"OutRun 2006 - Coast 2 Coast",
"Paper Mario - The Thousand Year Door",
"Pokémon HeartGold & SoulSilver",
"Resident Evil 2 (Remake)",
"Shenmue",
"Super Mario Galaxy",
"Trails in the Sky FC (Sora no Kiseki FC)",
"Trails in the Sky SC (Sora no Kiseki SC)",
"Turrican II - The Final Fight",
"Umineko no Naku Koro ni",
"Umineko no Naku Koro ni Chiru",
"VVVVVV",
"YS VIII - Lacrimosa of DANA",
"Yakuza 0",
"Ys (Chronicles)",
"Ys - Memories of Celceta",
"Ys - The Ark of Napishtim",
"Ys II (Chronicles)",
"Ys III - Wanderers from Ys (+The Oath in Felghana)",
"Ys IV - The Dawn of Ys",
"Ys Origin",
"Ys Seven",
"Ys X Nordics",
"Call of Duty: Modern Warfare",
"Half-Life 3",
})
// Test cases covering all match types
tests := []struct { tests := []struct {
query string query string
wantFound string want []string
}{ }{
// Exact substring matches // Exact (case-insensitive) substring
{"Metal", "Metal Gear Solid 4 - Guns of the Patriots"}, {"resident", []string{"Resident Evil 2 (Remake)"}},
{"Resident", "Resident Evil 2 (Remake)"}, // Ignoring spaces and special characters
{"Final", "Final Fantasy VII"}, {"joeandmac", []string{"Joe & Mac"}},
{"Ys", "Ys III - Wanderers from Ys (+The Oath in Felghana)"}, {"snes+mega", []string{"Lemmings (SNES + Mega Drive)"}},
{"pokemon", []string{"Pokémon HeartGold & SoulSilver"}},
// Contains matches (on cleaned terms) // Letters in order
{"gotp", "Metal Gear Solid 4 - Guns of the Patriots"}, {"codmw", []string{"Call of Duty: Modern Warfare"}},
{"mgs", "Metal Gear Solid 4 - Guns of the Patriots"}, {"mgs4gotp", []string{"Metal Gear Solid 4 - Guns of the Patriots"}},
{"kh", "Kingdom Hearts"}, {"re2r", []string{"Resident Evil 2 (Remake)"}},
{"mg", "Metal Gear Solid 4 - Guns of the Patriots"}, // No match
{"zzz", nil},
// Regex matches // Regex special characters are plain text
{"mgs4go", "Metal Gear Solid 4 - Guns of the Patriots"}, {"(remake)", []string{"Resident Evil 2 (Remake)"}},
{"re2r", "Resident Evil 2 (Remake)"}, {"[", nil},
{"ffv", "Final Fantasy VII"},
{"yiwfyt", "Ys III - Wanderers from Ys (+The Oath in Felghana)"},
{"codmw", "Call of Duty: Modern Warfare"},
{"mgs4gop", "Metal Gear Solid 4 - Guns of the Patriots"},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.query, func(t *testing.T) { t.Run(tt.query, func(t *testing.T) {
results := searchWithData(tt.query, testData) assert.Equal(t, tt.want, search(tt.query, data))
// Check that the expected item is in the results
found := false
for _, result := range results {
if result == tt.wantFound {
found = true
break
}
}
if !found {
t.Errorf("searchWithData(%q, ...) did not find expected item %q, got %v", tt.query, tt.wantFound, results)
}
}) })
} }
} }
// TestSearchOrdering tests that Pass 1 matches come before Pass 2 matches func TestSearchEmptyReturnsEverything(t *testing.T) {
func TestSearchOrdering(t *testing.T) { assert.Equal(t, soundtracks, search("", searchData(t)))
// "MG" matches Pass 1 (exact substring on original)
// "Metal Gear" matches Pass 2 (contains on cleaned) but NOT Pass 1 (no "mg" substring in "metal gear")
testData := searchWithDataHelper([]string{
"Metal Gear",
"MG",
})
results := searchWithData("mg", testData)
// Find indices
indexPass1 := -1
indexPass2 := -1
for i, result := range results {
if result == "MG" {
indexPass1 = i
}
if result == "Metal Gear" {
indexPass2 = i
}
}
// Pass 1 match (MG) should come before Pass 2 match (Metal Gear)
if indexPass1 >= 0 && indexPass2 >= 0 && indexPass1 >= indexPass2 {
t.Errorf("Expected Pass 1 match 'MG' to come before Pass 2 match 'Metal Gear', but got order: %v", results)
}
} }
// Note: The tests above verify: func TestSearchOrdering(t *testing.T) {
// 1. clean_term - the term cleaning logic // "MG" is an exact match, "Metal Gear Solid..." only matches by letters in order.
// 2. search - the complete normal search with exact, contains, and regex matching results := search("mg", searchData(t))
// 3. ordering - exact matches come before contains matches require.GreaterOrEqual(t, len(results), 2)
assert.Equal(t, "MG", results[0])
assert.Contains(t, results, "Metal Gear Solid 4 - Guns of the Patriots")
}
func TestSearchHandler(t *testing.T) {
handler := NewSearchHandler(newCache(soundtracks))
req := httptest.NewRequest(http.MethodPost, "/find", strings.NewReader(url.Values{"search_term": {"kingdom"}}.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "Kingdom Hearts")
assert.NotContains(t, rec.Body.String(), "Final Fantasy")
}
func TestSearchHandlerLoadError(t *testing.T) {
cache := backend.NewSearchCache(func(context.Context) ([]string, error) { return nil, errors.New("db down") })
rec := httptest.NewRecorder()
NewSearchHandler(cache).ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/find", nil))
assert.Equal(t, http.StatusInternalServerError, rec.Code)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

-60
View File
@@ -1,60 +0,0 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "./swagger.yaml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
// End Swagger UI call region
window.ui = ui;
};
</script>
</body>
</html>
-75
View File
@@ -1,75 +0,0 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
window.addEventListener('DOMContentLoaded', function () {
run();
});
</script>
</body>
</html>
-3
View File
@@ -1,3 +0,0 @@
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=479)}([function(e,t,n){"use strict";e.exports=n(555)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return u(e)?e:K(e)}function o(e){return s(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[f])}function u(e){return!(!e||!e[p])}function s(e){return!(!e||!e[h])}function c(e){return u(e)||s(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=u,n.isIndexed=s,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var f="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<<v,y=g-1,b={},w={value:!1},x={value:!1};function E(e){return e.value=!1,e}function _(e){e&&(e.value=!0)}function S(){}function k(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),o=0;o<n;o++)r[o]=e[o+t];return r}function A(e){return void 0===e.size&&(e.size=e.__iterate(C)),e.size}function O(e,t){if("number"!=typeof t){var n=t>>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function C(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return P(e,t,0)}function I(e,t){return P(e,t,t)}function P(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var N=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function z(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function U(){return{value:void 0,done:!0}}function q(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?u(e)?e.toSeq():e.fromEntrySeq():ue(e)}function Y(e){return null==e?ie():i(e)?u(e)?e.entrySeq():e.toIndexedSeq():se(e)}function G(e){return(null==e?ie():i(e)?u(e)?e.entrySeq():e:se(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=N,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return fe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return pe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return fe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return pe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Q,Z,X,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Q||(Q=new te([]))}function ue(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():V(e)?new oe(e).fromEntrySeq():q(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(Line truncated
//# sourceMappingURL=swagger-ui-bundle.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/universalModuleDefinition","webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/./node_modules/react/index.js","webpack://SwaggerUIBundle/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/./node_modules/prop-types/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/defaultMemoize.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/./node_modules/react-immutable-proptypes/dist/ImmutablePropTypes.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/classnames/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/hLine truncated
@@ -1,3 +0,0 @@
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=452)}([function(e,t){e.exports=require("react")},function(e,t){e.exports=require("immutable")},function(e,t,n){e.exports=n(492)},function(e,t,n){var r=n(178);e.exports=function(e,t,n){return t in e?r(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){e.exports=n(522)},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var r=n(178);function a(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),r(e,a.key,a)}}e.exports=function(e,t,n){return t&&a(e.prototype,t),n&&a(e,n),r(e,"prototype",{writable:!1}),e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){"use strict";(function(e){n.d(t,"z",(function(){return be})),n.d(t,"i",(function(){return Ee})),n.d(t,"v",(function(){return xe})),n.d(t,"r",(function(){return Se})),n.d(t,"t",(function(){return we})),n.d(t,"s",(function(){return je})),n.d(t,"p",(function(){return Oe})),n.d(t,"u",(function(){return Ce})),n.d(t,"x",(function(){return _e})),n.d(t,"y",(function(){return Ae})),n.d(t,"J",(function(){return ke})),n.d(t,"f",(function(){return Ie})),n.d(t,"n",(function(){return Pe})),n.d(t,"h",(function(){return Te})),n.d(t,"D",(function(){return Re})),n.d(t,"K",(function(){return Me})),n.d(t,"o",(function(){return ze})),n.d(t,"C",(function(){return Ve})),n.d(t,"a",(function(){return Fe})),n.d(t,"H",(function(){return Je})),n.d(t,"b",(function(){return We})),n.d(t,"G",(function(){return He})),n.d(t,"F",(function(){return $e})),n.d(t,"E",(function(){return Ke})),n.d(t,"k",(function(){return Ye})),n.d(t,"d",(function(){return Ge})),n.d(t,"g",(function(){return Ze})),n.d(t,"m",(function(){return Xe})),n.d(t,"l",(function(){return Qe})),n.d(t,"e",(function(){return et})),n.d(t,"I",(function(){return tt})),n.d(t,"w",(function(){return nt})),n.d(t,"A",(function(){return rt})),n.d(t,"B",(function(){return at})),n.d(t,"j",(function(){return ot})),n.d(t,"c",(function(){return it})),n.d(t,"q",(function(){return ct}));var r=n(14),a=n.n(r),o=(n(13),n(19)),i=n.n(o),s=n(56),c=n.n(s),u=n(22),l=n.n(u),p=n(4),f=n.n(p),d=n(83),h=n.n(d),m=n(2),v=n.n(m),g=n(23),y=n.n(g),b=n(12),E=n.n(b),x=n(15),S=n.n(x),w=(n(50),n(31)),j=n.n(w),O=n(24),C=n.n(O),_=n(194),A=n.n(_),k=n(21),I=n.n(k),P=n(74),T=n.n(P),R=(n(32),n(30)),N=n.n(R),M=n(20),q=n.n(M),D=n(55),B=n.n(D),U=n(106),L=n.n(U),z=n(104),V=n.n(z),F=n(1),J=n.n(F),W=n(418),H=n(419),$=n.n(H),K=n(244),Y=n.n(K),G=n(107),Z=n.n(G),X=n(420),Q=n.n(X),ee=n(306),te=n.n(ee),ne=n(102),re=n.n(ne),ae=n(103),oe=n.n(ae),ie=n(133),se=n(28),ce=n(424),ue=n.n(ce),le=n(138),pe=n(117),fe=n.n(pe),de=n(425),he=n.n(de),me=n(73),ve=n.n(me),ge="default",ye=function(e){return J.a.Iterable.isIterable(e)};function be(e){return we(e)?ye(e)?e.toJS():e:{}}function Ee(e){var t,n;if(ye(e))return e;if(e instanceof se.a.File)return e;if(!we(e))return e;if(l()(e))return f()(n=J.a.Seq(e)).call(n,Ee).toList();if(oe()(h()(e))){var r,a=function(e){if(!oe()(h()(e)))return e;var t,n={},r="_**[]",a={},o=c()(h()(e).call(e));try{for(o.s();!(t=o.n()).done;){var i=t.value;if(n[i[0]]||a[i[0]]&&a[i[0]].containsMultiple){var s,u,l,p;if(!a[i[0]])a[i[0]]={containsMultiple:!0,length:1},n[v()(l=v()(p="".concat(i[0])).call(p,r)).call(l,a[i[0]].length)]=n[i[0]],delete n[i[0]];a[i[0]].length+=1,n[v()(s=v()(u="".concat(i[0])).call(u,r)).call(s,a[i[0]].length)]=i[1]}else n[i[0]]=i[1]}}catch(e){o.e(e)}finally{o.f()}return n}(e);return f()(r=J.a.OrderedMap(a)).call(r,Ee)}return f()(t=J.a.OrderedMap(e)).call(t,Ee)}function xe(e){return l()(e)?e:[e]}function Se(e){return"function"==typeof e}function we(e){return!!e&&"object"===i()(e)}function je(e){return"function"==typeof e}function Oe(e){return l()(e)}var Ce=Z.a;function _e(e,t){var n;return j()(n=S()(e)).call(n,(function(n,r){return n[r]=t(e[r],r),n}),{})}function Ae(e,t){var n;return j()(n=S()(e)).call(n,(function(n,r){var a=t(e[r],r);retuLine truncated
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map
@@ -1 +0,0 @@
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/external \"react\"","webpack://SwaggerUIBundle/external \"immutable\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/external \"prop-types\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/external \"reselect\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/external \"react-immutable-proptypes\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/external \"classnames\"","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/get-built-in.js","webpack://SwaggerUIBundle/./node_modules/lodash/isArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/ruLine truncated
-3
View File
@@ -1,3 +0,0 @@
/*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */
module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=479)}([function(e,t,n){"use strict";e.exports=n(555)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return u(e)?e:K(e)}function o(e){return s(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[f])}function u(e){return!(!e||!e[p])}function s(e){return!(!e||!e[h])}function c(e){return u(e)||s(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=u,n.isIndexed=s,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var f="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<<v,y=g-1,b={},w={value:!1},x={value:!1};function E(e){return e.value=!1,e}function _(e){e&&(e.value=!0)}function S(){}function k(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),o=0;o<n;o++)r[o]=e[o+t];return r}function A(e){return void 0===e.size&&(e.size=e.__iterate(C)),e.size}function O(e,t){if("number"!=typeof t){var n=t>>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function C(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return P(e,t,0)}function I(e,t){return P(e,t,t)}function P(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var N=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function z(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function U(){return{value:void 0,done:!0}}function q(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?u(e)?e.toSeq():e.fromEntrySeq():ue(e)}function Y(e){return null==e?ie():i(e)?u(e)?e.entrySeq():e.toIndexedSeq():se(e)}function G(e){return(null==e?ie():i(e)?u(e)?e.entrySeq():e:se(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=N,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return fe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return pe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return fe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return pe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Q,Z,X,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Q||(Q=new te([]))}function ue(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():V(e)?new oe(e).fromEntrySeq():q(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function se(e){var t=le(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return tLine truncated
//# sourceMappingURL=swagger-ui-es-bundle.js.map
@@ -1 +0,0 @@
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/./node_modules/react/index.js","webpack://SwaggerUIBundle/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/./node_modules/prop-types/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/defaultMemoize.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/./node_modules/react-immutable-proptypes/dist/ImmutablePropTypes.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/classnames/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundlLine truncated
@@ -1,3 +0,0 @@
/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e():t.SwaggerUIStandalonePreset=e()}(this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/dist",n(n.s=247)}([function(t,e,n){"use strict";var r=n(1),i=n(72),o=n(6),u=n(15),s=n(74).f,a=n(256),c=n(16),f=n(25),l=n(51),p=n(20),h=function(t){var e=function(n,r,o){if(this instanceof e){switch(arguments.length){case 0:return new t;case 1:return new t(n);case 2:return new t(n,r)}return new t(n,r,o)}return i(t,this,arguments)};return e.prototype=t.prototype,e};t.exports=function(t,e){var n,i,d,y,v,g,w,M,_=t.target,L=t.global,x=t.stat,m=t.proto,j=L?r:x?r[_]:(r[_]||{}).prototype,b=L?c:c[_]||l(c,_,{})[_],N=b.prototype;for(d in e)n=!a(L?d:_+(x?".":"#")+d,t.forced)&&j&&p(j,d),v=b[d],n&&(g=t.noTargetGet?(M=s(j,d))&&M.value:j[d]),y=n&&g?g:e[d],n&&typeof v==typeof y||(w=t.bind&&n?f(y,r):t.wrap&&n?h(y):m&&u(y)?o(y):y,(t.sham||y&&y.sham||v&&v.sham)&&l(w,"sham",!0),l(b,d,w),m&&(p(c,i=_+"Prototype")||l(c,i,{}),l(c[i],d,y),t.real&&N&&!N[d]&&l(N,d,y)))}},function(t,e,n){(function(e){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||function(){return this}()||Function("return this")()}).call(this,n(33))},function(t,e,n){"use strict";t.exports=n(320)},function(t,e,n){t.exports=function(){"use strict";var t=Array.prototype.slice;function e(t,e){e&&(t.prototype=Object.create(e.prototype)),t.prototype.constructor=t}function n(t){return u(t)?t:J(t)}function r(t){return s(t)?t:Z(t)}function i(t){return a(t)?t:X(t)}function o(t){return u(t)&&!c(t)?t:V(t)}function u(t){return!(!t||!t[l])}function s(t){return!(!t||!t[p])}function a(t){return!(!t||!t[h])}function c(t){return s(t)||a(t)}function f(t){return!(!t||!t[d])}e(r,n),e(i,n),e(o,n),n.isIterable=u,n.isKeyed=s,n.isIndexed=a,n.isAssociative=c,n.isOrdered=f,n.Keyed=r,n.Indexed=i,n.Set=o;var l="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",y="delete",v=5,g=1<<v,w=g-1,M={},_={value:!1},L={value:!1};function x(t){return t.value=!1,t}function m(t){t&&(t.value=!0)}function j(){}function b(t,e){e=e||0;for(var n=Math.max(0,t.length-e),r=new Array(n),i=0;i<n;i++)r[i]=t[i+e];return r}function N(t){return void 0===t.size&&(t.size=t.__iterate(D)),t.size}function S(t,e){if("number"!=typeof e){var n=e>>>0;if(""+n!==e||4294967295===n)return NaN;e=n}return e<0?N(t)+e:e}function D(){return!0}function I(t,e,n){return(0===t||void 0!==n&&t<=-n)&&(void 0===e||void 0!==n&&e>=n)}function A(t,e){return C(t,e,0)}function T(t,e){return C(t,e,e)}function C(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}var E=0,O=1,z=2,k="function"==typeof Symbol&&Symbol.iterator,U="@@iterator",Y=k||U;function R(t){this.next=t}function P(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function Q(){return{value:void 0,done:!0}}function B(t){return!!W(t)}function F(t){return t&&"function"==typeof t.next}function G(t){var e=W(t);return e&&e.call(t)}function W(t){var e=t&&(k&&t[k]||t[U]);if("function"==typeof e)return e}function q(t){return t&&"number"==typeof t.length}function J(t){return null==t?ut():u(t)?t.toSeq():ct(t)}function Z(t){return null==t?ut().toKeyedSeq():u(t)?s(t)?t.toSeq():t.fromEntrySeq():st(t)}function X(t){return null==t?ut():u(t)?s(t)?t.entrySeq():t.toIndexedSeq():at(t)}function V(t){return(null==t?ut():u(t)?s(t)?t.entrySeq():t:at(t)).toSetSeq()}R.prototype.toString=function(){return"[Iterator]"},R.KEYS=E,R.VALUES=O,R.ENTRIES=z,R.prototype.inspect=R.prototype.toSource=function(){return this.toString()},R.prototype[Y]=function(){return this},e(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().Line truncated
//# sourceMappingURL=swagger-ui-standalone-preset.js.map
@@ -1 +0,0 @@
{"version":3,"sources":["webpack://SwaggerUIStandalonePreset/webpack/universalModuleDefinition","webpack://SwaggerUIStandalonePreset/webpack/bootstrap","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIStandalonePreset/./node_modules/react/index.js","webpack://SwaggerUIStandalonePreset/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIStandalonePreset/./src/core/utils.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/object-define-property.js","webpack://SwaggerUIStandalonePreset/./node_modules/inherits/inherits_browser.js","webpack://SwaggerUIStandalonePreset/./node_modules/safe-buffer/index.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/get-built-in.js","webpack://SwaggerUIStandalonePreset/(webpack)/buildin/global.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/length-of-array-like.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/isArray.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/get-map-iterator.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-indexed-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/array-iteration.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/_root.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/classof.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/memoize.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/classof-raw.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/create-non-enumerable-property.js","webpack://SwaggerUIStandalonePresetLine truncated
-4
View File
@@ -1,4 +0,0 @@
.swagger-ui{color:#3b4151;
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */font-family:sans-serif}.swagger-ui html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{-webkit-text-decoration-skip:objects;background-color:transparent}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eLine truncated
/*# sourceMappingURL=swagger-ui.css.map*/
-1
View File
@@ -1 +0,0 @@
{"version":3,"sources":["webpack:///./src/style/main.scss","webpack:///./src/style/_type.scss","webpack:///./src/style/_variables.scss","webpack:///./node_modules/tachyons-sass/scss/_normalize.scss","webpack:///./node_modules/tachyons-sass/scss/_debug-children.scss","webpack:///./node_modules/tachyons-sass/scss/_debug-grid.scss","webpack:///./node_modules/tachyons-sass/scss/_box-sizing.scss","webpack:///./node_modules/tachyons-sass/scss/_aspect-ratios.scss","webpack:///./node_modules/tachyons-sass/scss/_images.scss","webpack:///./node_modules/tachyons-sass/scss/_background-size.scss","webpack:///./node_modules/tachyons-sass/scss/_background-position.scss","webpack:///./node_modules/tachyons-sass/scss/_outlines.scss","webpack:///./node_modules/tachyons-sass/scss/_borders.scss","webpack:///./node_modules/tachyons-sass/scss/_border-colors.scss","webpack:///./node_modules/tachyons-sass/scss/_border-radius.scss","webpack:///./node_modules/tachyons-sass/scss/_border-style.scss","webpack:///./node_modules/tachyons-sass/scss/_border-widths.scss","webpack:///./node_modules/tachyons-sass/scss/_box-shadow.scss","webpack:///./node_modules/tachyons-sass/scss/_code.scss","webpack:///./node_modules/tachyons-sass/scss/_coordinates.scss","webpack:///./node_modules/tachyons-sass/scss/_clears.scss","webpack:///./node_modules/tachyons-sass/scss/_flexbox.scss","webpack:///./node_modules/tachyons-sass/scss/_display.scss","webpack:///./node_modules/tachyons-sass/scss/_floats.scss","webpack:///./node_modules/tachyons-sass/scss/_font-family.scss","webpack:///./node_modules/tachyons-sass/scss/_font-style.scss","webpack:///./node_modules/tachyons-sass/scss/_font-weight.scss","webpack:///./node_modules/tachyons-sass/scss/_forms.scss","webpack:///./node_modules/tachyons-sass/scss/_heights.scss","webpack:///./node_modules/tachyons-sass/scss/_letter-spacing.scss","webpack:///./node_modules/tachyons-sass/scss/_line-height.scss","webpack:///./node_modules/tachyons-sass/scss/_links.scss","webpack:///./node_modules/tachyons-sass/scss/_lists.scss","webpack:///./node_modules/tachyons-sass/scss/_max-widths.scss","webpack:///./node_modules/tachyons-sass/scss/_widths.scss","webpack:///./node_modules/tachyons-sass/scss/_overflow.scss","webpack:///./node_modules/tachyons-sass/scss/_position.scss","webpack:///./node_modules/tachyons-sass/scss/_opacity.scss","webpack:///./node_modules/tachyons-sass/scss/_rotations.scss","webpack:///./node_modules/tachyons-sass/scss/_skins.scss","webpack:///./node_modules/tachyons-sass/scss/_skins-pseudo.scss","webpack:///./node_modules/tachyons-sass/scss/_spacing.scss","webpack:///./node_modules/tachyons-sass/scss/_variables.scss","webpack:///./node_modules/tachyons-sass/scss/_negative-margins.scss","webpack:///./node_modules/tachyons-sass/scss/_tables.scss","webpack:///./node_modules/tachyons-sass/scss/_text-decoration.scss","webpack:///./node_modules/tachyons-sass/scss/_text-align.scss","webpack:///./node_modules/tachyons-sass/scss/_text-transform.scss","webpack:///./node_modules/tachyons-sass/scss/_type-scale.scss","webpack:///./node_modules/tachyons-sass/scss/_typography.scss","webpack:///./node_modules/tachyons-sass/scss/_utilities.scss","webpack:///./node_modules/tachyons-sass/scss/_visibility.scss","webpack:///./node_modules/tachyons-sass/scss/_white-space.scss","webpack:///./node_modules/tachyons-sass/scss/_vertical-align.scss","webpack:///./node_modules/tachyons-sass/scss/_hovers.scss","webpack:///./node_modules/tachyons-sass/scss/_z-index.scss","webpack:///./node_modules/tachyons-sass/scss/_nested.scss","webpack:///./src/style/_layout.scss","webpack:///./src/style/_buttons.scss","webpack:///./src/style/_mixins.scss","webpack:///./src/style/_form.scss","webpack:///./src/style/_modal.scss","webpack:///./src/style/_models.scss","webpack:///./src/style/_servers.scss","webpack:///./src/style/_table.scss","webpack:///./src/style/_topbar.scss","webpack:///./src/style/_information.scss","webpack:///./src/style/_authorize.scss","webpack:///./src/style/_errors.scss","webpack:///./src/style/_split-pane-mode.scss","webpack:///./src/style/_markdown.scss"],"names":[],"mappings":"AAAA,YCII,aCYU;ECTd,4EFLI,sBDqhCJ,CGrgCA,iBAEE,0BACA,8BAFA,gBH4gCF,CGhgCA,iBACE,QHkgCF,CG3/BA,gHAME,aH6/BF,CGr/BA,eACE,cACA,cHu/BF,CG5+BA,2DAGE,aH++BF,CGx+BA,mBACE,eH0+BF,CGl+BA,eACE,uBACA,SACA,gBHu+BF,CG/9BA,gBACE,gCACA,aHm+BF,CGx9BA,cAEE,qCADA,4BH69BF,CGp9BA,wBACE,mBACA,0BACA,yEHy9BF,CGl9BA,iCAEE,oBASA,kBH28BF,CGn8BA,kDAGE,gCACA,aH28BF,CGp8BA,gBACE,iBHs8BF,CG/7BA,iBACE,sBACA,UHi8BF,CG17BA,kBACE,aH47BF,CGp7BA,gCAEE,cACA,cACA,kBACA,uBHs7BF,CGn7BA,gBACE,aHq7BF,CGl7BA,gBACE,SHo7BF,CG16BA,oCAEE,oBH46BF,CGr6BA,kCACE,aACA,QHu6BF,CGh6BA,gBACE,iBHk6BF,CG35BA,2BACE,eH65BF,CGl5BA,kGAKE,uBACA,eACA,iBACA,QHw5BF,CGh5BA,qCAEE,gBHm5BF,CG34BA,sCAEE,mBH84BF,CGr4BA,qGAIE,yBHw4BF,CGj4BA,wKAIE,kBACA,SHm4BF,CG53BA,4JAIE,6BH83BF,CGv3BA,qBACE,0BHy3BF,CG/2BA,mBACE,sBACA,cACA,cACA,eACA,UACA,kBHu3BF,CG/2BA,qBACE,qBACA,uBHm3BF,CG52BA,qBACE,aH82BF,CGt2BA,qDAEE,sBACA,SH02BLine truncated
-3
View File
@@ -1,3 +0,0 @@
/*! For license information please see swagger-ui.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUICore=t():e.SwaggerUICore=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=452)}([function(e,t){e.exports=require("react")},function(e,t){e.exports=require("immutable")},function(e,t,n){e.exports=n(492)},function(e,t,n){var r=n(178);e.exports=function(e,t,n){return t in e?r(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){e.exports=n(522)},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var r=n(178);function a(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),r(e,a.key,a)}}e.exports=function(e,t,n){return t&&a(e.prototype,t),n&&a(e,n),r(e,"prototype",{writable:!1}),e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){"use strict";(function(e){n.d(t,"z",(function(){return be})),n.d(t,"i",(function(){return Ee})),n.d(t,"v",(function(){return xe})),n.d(t,"r",(function(){return Se})),n.d(t,"t",(function(){return we})),n.d(t,"s",(function(){return je})),n.d(t,"p",(function(){return Oe})),n.d(t,"u",(function(){return Ce})),n.d(t,"x",(function(){return _e})),n.d(t,"y",(function(){return Ae})),n.d(t,"J",(function(){return ke})),n.d(t,"f",(function(){return Ie})),n.d(t,"n",(function(){return Pe})),n.d(t,"h",(function(){return Te})),n.d(t,"D",(function(){return Re})),n.d(t,"K",(function(){return Me})),n.d(t,"o",(function(){return ze})),n.d(t,"C",(function(){return Ve})),n.d(t,"a",(function(){return Fe})),n.d(t,"H",(function(){return Je})),n.d(t,"b",(function(){return We})),n.d(t,"G",(function(){return He})),n.d(t,"F",(function(){return $e})),n.d(t,"E",(function(){return Ke})),n.d(t,"k",(function(){return Ye})),n.d(t,"d",(function(){return Ge})),n.d(t,"g",(function(){return Ze})),n.d(t,"m",(function(){return Xe})),n.d(t,"l",(function(){return Qe})),n.d(t,"e",(function(){return et})),n.d(t,"I",(function(){return tt})),n.d(t,"w",(function(){return nt})),n.d(t,"A",(function(){return rt})),n.d(t,"B",(function(){return at})),n.d(t,"j",(function(){return ot})),n.d(t,"c",(function(){return it})),n.d(t,"q",(function(){return ct}));var r=n(14),a=n.n(r),o=(n(13),n(19)),i=n.n(o),s=n(56),c=n.n(s),u=n(22),l=n.n(u),p=n(4),f=n.n(p),d=n(83),h=n.n(d),m=n(2),v=n.n(m),g=n(23),y=n.n(g),b=n(12),E=n.n(b),x=n(15),S=n.n(x),w=(n(50),n(31)),j=n.n(w),O=n(24),C=n.n(O),_=n(194),A=n.n(_),k=n(21),I=n.n(k),P=n(74),T=n.n(P),R=(n(32),n(30)),N=n.n(R),M=n(20),q=n.n(M),D=n(55),B=n.n(D),U=n(106),L=n.n(U),z=n(104),V=n.n(z),F=n(1),J=n.n(F),W=n(418),H=n(419),$=n.n(H),K=n(244),Y=n.n(K),G=n(107),Z=n.n(G),X=n(420),Q=n.n(X),ee=n(306),te=n.n(ee),ne=n(102),re=n.n(ne),ae=n(103),oe=n.n(ae),ie=n(133),se=n(28),ce=n(424),ue=n.n(ce),le=n(138),pe=n(117),fe=n.n(pe),de=n(425),he=n.n(de),me=n(73),ve=n.n(me),ge="default",ye=function(e){return J.a.Iterable.isIterable(e)};function be(e){return we(e)?ye(e)?e.toJS():e:{}}function Ee(e){var t,n;if(ye(e))return e;if(e instanceof se.a.File)return e;if(!we(e))return e;if(l()(e))return f()(n=J.a.Seq(e)).call(n,Ee).toList();if(oe()(h()(e))){var r,a=function(e){if(!oe()(h()(e)))return e;var t,n={},r="_**[]",a={},o=c()(h()(e).call(e));try{for(o.s();!(t=o.n()).done;){var i=t.value;if(n[i[0]]||a[i[0]]&&a[i[0]].containsMultiple){var s,u,l,p;if(!a[i[0]])a[i[0]]={containsMultiple:!0,length:1},n[v()(l=v()(p="".concat(i[0])).call(p,r)).call(l,a[i[0]].length)]=n[i[0]],delete n[i[0]];a[i[0]].length+=1,n[v()(s=v()(u="".concat(i[0])).call(u,r)).call(s,a[i[0]].length)]=i[1]}else n[i[0]]=i[1]}}catch(e){o.e(e)}finally{o.f()}return n}(e);return f()(r=J.a.OrderedMap(a)).call(r,Ee)}return f()(t=J.a.OrderedMap(e)).call(t,Ee)}function xe(e){return l()(e)?e:[e]}function Se(e){return"function"==typeof e}function we(e){return!!e&&"object"===i()(e)}function je(e){return"function"==typeof e}functiLine truncated
//# sourceMappingURL=swagger-ui.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"sources":["webpack://SwaggerUICore/webpack/universalModuleDefinition","webpack://SwaggerUICore/webpack/bootstrap","webpack://SwaggerUICore/external \"react\"","webpack://SwaggerUICore/external \"immutable\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUICore/./src/core/utils.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUICore/external \"prop-types\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUICore/external \"reselect\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUICore/./src/core/window.js","webpack://SwaggerUICore/external \"react-immutable-proptypes\"","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUICore/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUICore/./node_modules/lodash/get.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUICore/./src/core/plugins/spec/actions.js","webpack://SwaggerUICore/external \"classnames\"","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUICore/./node_modules/core-js-pure/internals/get-built-in.js","webpack://SwaggerUICore/./node_modules/lodash/isArray.js","webpack://SwaggerUICore/./node_modules/@babel/runtime-corejs3/helpers/objectWithoutProperties.js","webpack://SLine truncated
-346
View File
@@ -1,346 +0,0 @@
openapi: 3.0.3
info:
version: "3.2"
title: "Music Server"
description: "Upgraded Go version and the version of all dependencies. Fixed som more bugs."
contact:
email: "zarnor91@gmail.com"
servers:
- url: https://music.sanplex.xyz/
description: Main (production) server
- url: https://tmusic.sanplex.xyz/
description: Test server
- url: http://localhost:8080
description: Local server
tags:
- name: "Music"
description: "Endpoints for the music"
- name: "Version"
description: "Information about the application"
- name: "Sync"
description: "Sync the database with the folders"
paths:
/music:
get:
tags:
- "Music"
summary: "Get song"
description: "Get a specified song from the que, if invalid number it will be the first or the last song in que"
operationId: "getSong"
parameters:
- name: "song"
in: "query"
schema:
type: integer
description: "The number the song has in the que"
required: true
responses:
"200":
description: "The specified file"
content:
audio/mpeg:
schema:
type: object
format: binary
"500":
description: "Something went wrong on the server"
/music/soundTest:
get:
tags:
- "Music"
summary: "Start a sound test"
description: "Get a sound check song"
operationId: "getSoundCheckSong"
responses:
"200":
description: "A file"
content:
audio/mpeg:
schema:
type: object
format: binary
"500":
description: "Something went wrong on the server"
/music/rand:
get:
tags:
- "Music"
summary: "Get random song"
description: "Takes a random song from a random game"
operationId: "getRandom"
responses:
"200":
description: "A file"
content:
audio/mpeg:
schema:
type: object
format: binary
"500":
description: "Something went wrong on the server"
/music/rand/low:
get:
tags:
- "Music"
summary: "Get random song"
description: "Takes a random song from a random game but increases the chance for games that haven't been played"
operationId: "getRandomLow"
responses:
"200":
description: "A file"
content:
audio/mpeg:
schema:
type: object
format: binary
"500":
description: "Something went wrong on the server"
/music/info:
get:
tags:
- "Music"
summary: "Get info of current song"
description: "Return the info of the song currently playing"
operationId: "getInfo"
responses:
"200":
description: "Info of current song"
content:
application/json:
schema:
$ref: '#/components/schemas/info'
"500":
description: "Something went wrong on the server"
/music/list:
get:
tags:
- "Music"
summary: "Gets song que"
description: "Gets a list of all songs that have been fetched"
operationId: "getList"
responses:
"200":
description: "A list"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/list'
"500":
description: "Something went wrong on the server"
/music/next:
get:
tags:
- "Music"
summary: "Get next song"
description: "Gets next song in the song que, if there is no more a new random song will be generated."
operationId: "getNext"
responses:
"200":
description: "A file"
content:
audio/mpeg:
schema:
type: object
format: binary
"500":
description: "Something went wrong on the server"
/music/previous:
get:
tags:
- "Music"
summary: "Get previous song"
description: "Gets previous song in the song que, if there is no more the first song will be returns."
operationId: "getPrevious"
responses:
"200":
description: "A file"
content:
audio/mpeg:
schema:
type: object
format: binary
"500":
description: "Something went wrong on the server"
/music/all:
get:
tags:
- "Music"
summary: "Gets all games"
description: "Gets a list of all games that is in the database"
operationId: "getAll"
responses:
"200":
description: "A list"
content:
application/json:
schema:
type: array
items:
type: string
example: ["God of War", "Final Fantasy VII"]
"500":
description: "Something went wrong on the server"
/music/all/order:
get:
tags:
- "Music"
summary: "Gets all games in order"
description: "Gets a ordered list of all games that is in the database"
operationId: "getAll"
responses:
"200":
description: "A list"
content:
application/json:
schema:
type: array
items:
type: string
example: [ "God of War", "Final Fantasy VII" ]
"500":
description: "Something went wrong on the server"
/music/all/random:
get:
tags:
- "Music"
summary: "Gets all games in random order"
description: "Gets a list of all games that is in the database in random order"
operationId: "getAllRandom"
responses:
"200":
description: "A list"
content:
application/json:
schema:
type: array
items:
type: string
example: ["Final Fantasy VII", "God of War"]
"500":
description: "Something went wrong on the server"
/music/played:
put:
tags:
- "Music"
summary: "Increase played"
description: "Increase the number of times the current game has been played"
operationId: "setPlayed"
responses:
"202":
description: "Accepted"
"401":
description: "Bad Request"
"500":
description: "Something went wrong on the server"
/music/addQue:
get:
tags:
- "Music"
summary: "Adds last song to que"
description: "Adds the last fetched song to the song que"
operationId: "addQue"
responses:
"200":
description: "OK"
"401":
description: "Bad Request"
"500":
description: "Something went wrong on the server"
/music/reset:
get:
tags:
- "Music"
summary: "Resets the song que"
description: "Resets the song que for a new match"
operationId: "reset"
responses:
"200":
description: "successful operation"
"500":
description: "Something went wrong on the server"
/version:
get:
tags:
- "Version"
summary: "Returns pet inventories by status"
description: "Returns a map of status codes to quantities"
operationId: "getVersionInfo"
responses:
"200":
description: "successful operation"
content:
application/json:
schema:
$ref: '#/components/schemas/versionInfo'
/sync:
get:
tags:
- "Sync"
summary: "Sync games"
description: "Sync the database with the folders"
operationId: "sync"
responses:
"200":
description: "successful operation"
"500":
description: "Something went wrong on the server"
components:
schemas:
list:
type: object
properties:
Game:
type: string
example: God of War
Song:
type: string
example: Main Theme.mp3
CurrentlyPlaying:
type: boolean
example: true
description: Only included on the one that's playing
SongNo:
type: integer
example: 3
info:
type: object
properties:
Game:
type: string
example: God of War
GamePlayed:
type: integer
example: 10
Song:
type: string
example: Main Theme.mp3
SongPlayed:
type: integer
example: 10
SongNo:
type: integer
example: 3
versionInfo:
type: object
properties:
version:
type: string
example: 0.5.1
changelog:
type: string
example: "Added shadow to build fat jars. Added support to give property files through arguments."
history:
type: array
items:
$ref: '#/components/schemas/historyInfo'
historyInfo:
type: object
properties:
version:
type: string
example: 0.5.0
changelog:
type: string
example: "Updated kotlin version, gradle version and al libs. Added connection to database with exposed. Added new endpoints."
-21
View File
@@ -1,21 +0,0 @@
version: '3.8'
services:
test-db:
image: postgres:15-alpine
environment:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: music_server_test
ports:
- "5433:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- test-db-data:/var/lib/postgresql/data
volumes:
test-db-data:
+25 -5
View File
@@ -9,8 +9,7 @@ require (
github.com/golang-migrate/migrate/v4 v4.19.1 github.com/golang-migrate/migrate/v4 v4.19.1
github.com/jackc/pgx/v5 v5.9.2 github.com/jackc/pgx/v5 v5.9.2
github.com/labstack/echo/v5 v5.1.1 github.com/labstack/echo/v5 v5.1.1
github.com/lib/pq v1.12.3 github.com/lib/pq v1.12.3 // indirect
github.com/panjf2000/ants/v2 v2.12.0
github.com/spf13/afero v1.15.0 github.com/spf13/afero v1.15.0
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/swaggo/echo-swagger/v2 v2.0.1 github.com/swaggo/echo-swagger/v2 v2.0.1
@@ -19,25 +18,34 @@ require (
go.uber.org/zap v1.28.0 go.uber.org/zap v1.28.0
) )
require github.com/google/uuid v1.6.0 require (
github.com/google/uuid v1.6.0
golang.org/x/sync v0.20.0
)
require ( require (
dario.cat/mergo v1.0.2 // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.10.0 // indirect github.com/ebitengine/purego v0.10.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-errors/errors v1.5.1 // indirect github.com/go-errors/errors v1.5.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
@@ -52,12 +60,15 @@ require (
github.com/go-openapi/swag/stringutils v0.26.0 // indirect github.com/go-openapi/swag/stringutils v0.26.0 // indirect
github.com/go-openapi/swag/typeutils v0.26.0 // indirect github.com/go-openapi/swag/typeutils v0.26.0 // indirect
github.com/go-openapi/swag/yamlutils v0.26.0 // indirect github.com/go-openapi/swag/yamlutils v0.26.0 // indirect
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/compress v1.18.5 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.54.1 // indirect github.com/moby/moby/api v1.54.1 // indirect
@@ -67,10 +78,12 @@ require (
github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
github.com/natefinch/atomic v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil/v4 v4.26.3 // indirect github.com/shirou/gopsutil/v4 v4.26.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect github.com/sirupsen/logrus v1.9.4 // indirect
github.com/sv-tools/openapi v0.4.0 // indirect github.com/sv-tools/openapi v0.4.0 // indirect
@@ -78,6 +91,8 @@ require (
github.com/swaggo/swag/v2 v2.0.0-rc5 // indirect github.com/swaggo/swag/v2 v2.0.0-rc5 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
github.com/urfave/cli/v2 v2.27.5 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
@@ -87,9 +102,9 @@ require (
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.51.0 // indirect
golang.org/x/mod v0.36.0 // indirect golang.org/x/mod v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/net v0.54.0 // indirect
golang.org/x/sys v0.44.0 // indirect golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
@@ -97,3 +112,8 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect
) )
tool (
github.com/a-h/templ/cmd/templ
github.com/swaggo/swag/cmd/swag
)
Loaded 100 of 200 files, more files were not shown because too many files have changed in this diff. Show more