Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9256b7fe4b | |||
| 2bc9012a01 | |||
| 26a1cf9c76 | |||
| d459d796cf | |||
| 90d621c195 | |||
| c63202242b | |||
| 3418f492f5 | |||
| f4d1c3cf28 | |||
| 98c1948eff | |||
| 3e37303979 |
+11
-17
@@ -1,35 +1,26 @@
|
|||||||
# Stage 1: Build frontend
|
|
||||||
FROM node:18-alpine AS frontend-builder
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
WORKDIR /app
|
|
||||||
RUN git clone https://gitea.sanplex.xyz/Sansan/MusicFrontend.git
|
|
||||||
WORKDIR /app/MusicFrontend
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
# Generate config.js with empty API_HOSTNAME (relative paths)
|
|
||||||
RUN echo "window.__RUNTIME_CONFIG__ = { API_HOSTNAME: '' };" > dist/config.js
|
|
||||||
|
|
||||||
# Stage 2: Build backend
|
|
||||||
FROM golang:1.25-alpine as build_go
|
FROM golang:1.25-alpine as build_go
|
||||||
RUN apk add --no-cache curl
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN go install github.com/a-h/templ/cmd/templ@latest
|
RUN go install github.com/a-h/templ/cmd/templ@latest
|
||||||
|
|
||||||
RUN templ generate
|
RUN templ generate
|
||||||
|
|
||||||
RUN go build -o main cmd/main.go
|
RUN go build -o main cmd/main.go
|
||||||
|
|
||||||
# Stage 3: Final image
|
# Stage 2, distribution container
|
||||||
FROM golang:1.25-alpine
|
FROM golang:1.25-alpine
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
VOLUME /sorted
|
VOLUME /sorted
|
||||||
|
VOLUME /frontend
|
||||||
VOLUME /characters
|
VOLUME /characters
|
||||||
|
|
||||||
COPY --from=build_go /app/main .
|
|
||||||
COPY --from=frontend-builder /app/MusicFrontend/dist /frontend
|
|
||||||
COPY ./songs/ ./songs/
|
|
||||||
|
|
||||||
ENV PORT 8080
|
ENV PORT 8080
|
||||||
ENV DB_HOST ""
|
ENV DB_HOST ""
|
||||||
ENV DB_PORT ""
|
ENV DB_PORT ""
|
||||||
@@ -39,4 +30,7 @@ ENV DB_NAME ""
|
|||||||
ENV MUSIC_PATH ""
|
ENV MUSIC_PATH ""
|
||||||
ENV CHARACTERS_PATH ""
|
ENV CHARACTERS_PATH ""
|
||||||
|
|
||||||
|
COPY --from=build_go /app/main .
|
||||||
|
COPY ./songs/ ./songs/
|
||||||
|
|
||||||
CMD ./main
|
CMD ./main
|
||||||
|
|||||||
+39
-676
@@ -23,539 +23,6 @@ var doc = `{
|
|||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/v1/statistics/games/last-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the most recently played games",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get last played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/least-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N least played games with their songs",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get least played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/most-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N most played games with their songs",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get most played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/never-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns all games that have never been played (times_played = 0)",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get never played games",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/oldest-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the least recently played games (that have been played at least once)",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get oldest played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/songs/least-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N least played songs with their game info",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get least played songs",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.SongInfoForStats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/songs/most-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N most played songs with their game info",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get most played songs",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.SongInfoForStats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/summary": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns overall statistics about the music library",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get statistics summary",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/backend.StatisticsSummary"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/token": {
|
|
||||||
"post": {
|
|
||||||
"description": "Returns a new session token for API access",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"auth"
|
|
||||||
],
|
|
||||||
"summary": "Create session token",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Client type",
|
|
||||||
"name": "request",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/server.TokenRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/server.TokenResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"description": "Deletes the current session token",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"auth"
|
|
||||||
],
|
|
||||||
"summary": "Invalidate session token",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Bearer token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Unauthorized",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/token/cleanup": {
|
|
||||||
"post": {
|
|
||||||
"description": "Removes all expired session tokens from the database",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"auth"
|
|
||||||
],
|
|
||||||
"summary": "Cleanup expired sessions",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Bearer token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Unauthorized",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/character": {
|
"/character": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns the image for a specific character",
|
"description": "Returns the image for a specific character",
|
||||||
@@ -614,6 +81,29 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/dbtest": {
|
||||||
|
"get": {
|
||||||
|
"description": "Tests the database connection",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"database"
|
||||||
|
],
|
||||||
|
"summary": "Test database connection",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "TestedDB",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/download": {
|
"/download": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Checks for the latest version of the application",
|
"description": "Checks for the latest version of the application",
|
||||||
@@ -834,7 +324,7 @@ var doc = `{
|
|||||||
"tags": [
|
"tags": [
|
||||||
"music"
|
"music"
|
||||||
],
|
],
|
||||||
"summary": "Get all soundtracks",
|
"summary": "Get all games",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -867,7 +357,7 @@ var doc = `{
|
|||||||
"tags": [
|
"tags": [
|
||||||
"music"
|
"music"
|
||||||
],
|
],
|
||||||
"summary": "Get all soundtracks random",
|
"summary": "Get all games random",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -1207,10 +697,10 @@ var doc = `{
|
|||||||
"tags": [
|
"tags": [
|
||||||
"sync"
|
"sync"
|
||||||
],
|
],
|
||||||
"summary": "Sync soundtracks with only changes",
|
"summary": "Sync games with only changes",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Start syncing soundtracks",
|
"description": "Start syncing games",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -1239,7 +729,7 @@ var doc = `{
|
|||||||
"summary": "Sync all games fully",
|
"summary": "Sync all games fully",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Start syncing soundtracks full",
|
"description": "Start syncing games full",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -1289,10 +779,10 @@ var doc = `{
|
|||||||
"tags": [
|
"tags": [
|
||||||
"sync"
|
"sync"
|
||||||
],
|
],
|
||||||
"summary": "Reset soundtracks database",
|
"summary": "Reset games database",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Soundtracks and songs are deleted from the database",
|
"description": "Games and songs are deleted from the database",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -1308,7 +798,7 @@ var doc = `{
|
|||||||
},
|
},
|
||||||
"/version": {
|
"/version": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "get latest version info",
|
"description": "get string by ID",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1316,9 +806,9 @@ var doc = `{
|
|||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"version"
|
"accounts"
|
||||||
],
|
],
|
||||||
"summary": "Getting the latest version of the backend",
|
"summary": "Getting the version of the backend",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -1334,154 +824,27 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/version/history": {
|
|
||||||
"get": {
|
|
||||||
"description": "get version history",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"version"
|
|
||||||
],
|
|
||||||
"summary": "Getting the version history of the backend",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.VersionData"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"backend.GameWithSongs": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"game_id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"game_last_played": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_played": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"songs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.SongInfoForStats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend.SongInfoForStats": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"game_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"song_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"times_played": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend.StatisticsSummary": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"avg_game_plays": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"max_game_plays": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"min_game_plays": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"never_played_games": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"played_games": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_game_plays": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_games": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend.VersionData": {
|
"backend.VersionData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"changelog": {
|
"changelog": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "account name"
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/backend.VersionData"
|
||||||
},
|
}
|
||||||
"example": [
|
|
||||||
"[\"Initial release\"",
|
|
||||||
"\"Bug fixes\"]"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1.0.0"
|
"example": "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"server.TokenRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"client_type": {
|
|
||||||
"description": "Optional: \"web\", \"mobile\", \"api\"",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"server.TokenResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"client_type": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"expires_at": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
+39
-676
@@ -4,539 +4,6 @@
|
|||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/v1/statistics/games/last-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the most recently played games",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get last played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/least-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N least played games with their songs",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get least played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/most-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N most played games with their songs",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get most played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/never-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns all games that have never been played (times_played = 0)",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get never played games",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/games/oldest-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the least recently played games (that have been played at least once)",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get oldest played games",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.GameWithSongs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/songs/least-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N least played songs with their game info",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get least played songs",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.SongInfoForStats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/songs/most-played": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns the top N most played songs with their game info",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get most played songs",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Number of results (default: 10)",
|
|
||||||
"name": "limit",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.SongInfoForStats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/statistics/summary": {
|
|
||||||
"get": {
|
|
||||||
"description": "Returns overall statistics about the music library",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"statistics"
|
|
||||||
],
|
|
||||||
"summary": "Get statistics summary",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/backend.StatisticsSummary"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/token": {
|
|
||||||
"post": {
|
|
||||||
"description": "Returns a new session token for API access",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"auth"
|
|
||||||
],
|
|
||||||
"summary": "Create session token",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Client type",
|
|
||||||
"name": "request",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/server.TokenRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/server.TokenResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad Request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"description": "Deletes the current session token",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"auth"
|
|
||||||
],
|
|
||||||
"summary": "Invalidate session token",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Bearer token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Unauthorized",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/token/cleanup": {
|
|
||||||
"post": {
|
|
||||||
"description": "Removes all expired session tokens from the database",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"auth"
|
|
||||||
],
|
|
||||||
"summary": "Cleanup expired sessions",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Bearer token",
|
|
||||||
"name": "Authorization",
|
|
||||||
"in": "header",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Unauthorized",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal Server Error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/character": {
|
"/character": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns the image for a specific character",
|
"description": "Returns the image for a specific character",
|
||||||
@@ -595,6 +62,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/dbtest": {
|
||||||
|
"get": {
|
||||||
|
"description": "Tests the database connection",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"database"
|
||||||
|
],
|
||||||
|
"summary": "Test database connection",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "TestedDB",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/download": {
|
"/download": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Checks for the latest version of the application",
|
"description": "Checks for the latest version of the application",
|
||||||
@@ -815,7 +305,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"music"
|
"music"
|
||||||
],
|
],
|
||||||
"summary": "Get all soundtracks",
|
"summary": "Get all games",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -848,7 +338,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"music"
|
"music"
|
||||||
],
|
],
|
||||||
"summary": "Get all soundtracks random",
|
"summary": "Get all games random",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -1188,10 +678,10 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"sync"
|
"sync"
|
||||||
],
|
],
|
||||||
"summary": "Sync soundtracks with only changes",
|
"summary": "Sync games with only changes",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Start syncing soundtracks",
|
"description": "Start syncing games",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -1220,7 +710,7 @@
|
|||||||
"summary": "Sync all games fully",
|
"summary": "Sync all games fully",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Start syncing soundtracks full",
|
"description": "Start syncing games full",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -1270,10 +760,10 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"sync"
|
"sync"
|
||||||
],
|
],
|
||||||
"summary": "Reset soundtracks database",
|
"summary": "Reset games database",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Soundtracks and songs are deleted from the database",
|
"description": "Games and songs are deleted from the database",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@@ -1289,7 +779,7 @@
|
|||||||
},
|
},
|
||||||
"/version": {
|
"/version": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "get latest version info",
|
"description": "get string by ID",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1297,9 +787,9 @@
|
|||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"version"
|
"accounts"
|
||||||
],
|
],
|
||||||
"summary": "Getting the latest version of the backend",
|
"summary": "Getting the version of the backend",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -1315,154 +805,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/version/history": {
|
|
||||||
"get": {
|
|
||||||
"description": "get version history",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"version"
|
|
||||||
],
|
|
||||||
"summary": "Getting the version history of the backend",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.VersionData"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"backend.GameWithSongs": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"game_id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"game_last_played": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_played": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"songs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/backend.SongInfoForStats"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend.SongInfoForStats": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"game_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"song_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"times_played": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend.StatisticsSummary": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"avg_game_plays": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"max_game_plays": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"min_game_plays": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"never_played_games": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"played_games": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_game_plays": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total_games": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backend.VersionData": {
|
"backend.VersionData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"changelog": {
|
"changelog": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "account name"
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/backend.VersionData"
|
||||||
},
|
}
|
||||||
"example": [
|
|
||||||
"[\"Initial release\"",
|
|
||||||
"\"Bug fixes\"]"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1.0.0"
|
"example": "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"server.TokenRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"client_type": {
|
|
||||||
"description": "Optional: \"web\", \"mobile\", \"api\"",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"server.TokenResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"client_type": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"expires_at": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+29
-448
@@ -1,433 +1,20 @@
|
|||||||
definitions:
|
definitions:
|
||||||
backend.GameWithSongs:
|
|
||||||
properties:
|
|
||||||
game_id:
|
|
||||||
type: integer
|
|
||||||
game_last_played:
|
|
||||||
type: string
|
|
||||||
game_name:
|
|
||||||
type: string
|
|
||||||
game_played:
|
|
||||||
type: integer
|
|
||||||
songs:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.SongInfoForStats'
|
|
||||||
type: array
|
|
||||||
type: object
|
|
||||||
backend.SongInfoForStats:
|
|
||||||
properties:
|
|
||||||
file_name:
|
|
||||||
type: string
|
|
||||||
game_id:
|
|
||||||
type: integer
|
|
||||||
game_name:
|
|
||||||
type: string
|
|
||||||
path:
|
|
||||||
type: string
|
|
||||||
song_name:
|
|
||||||
type: string
|
|
||||||
times_played:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
backend.StatisticsSummary:
|
|
||||||
properties:
|
|
||||||
avg_game_plays:
|
|
||||||
type: number
|
|
||||||
max_game_plays:
|
|
||||||
type: integer
|
|
||||||
min_game_plays:
|
|
||||||
type: integer
|
|
||||||
never_played_games:
|
|
||||||
type: integer
|
|
||||||
played_games:
|
|
||||||
type: integer
|
|
||||||
total_game_plays:
|
|
||||||
type: integer
|
|
||||||
total_games:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
backend.VersionData:
|
backend.VersionData:
|
||||||
properties:
|
properties:
|
||||||
changelog:
|
changelog:
|
||||||
example:
|
example: account name
|
||||||
- '["Initial release"'
|
|
||||||
- '"Bug fixes"]'
|
|
||||||
items:
|
|
||||||
type: string
|
type: string
|
||||||
|
history:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/backend.VersionData'
|
||||||
type: array
|
type: array
|
||||||
version:
|
version:
|
||||||
example: 1.0.0
|
example: 1.0.0
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
server.TokenRequest:
|
|
||||||
properties:
|
|
||||||
client_type:
|
|
||||||
description: 'Optional: "web", "mobile", "api"'
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
server.TokenResponse:
|
|
||||||
properties:
|
|
||||||
client_type:
|
|
||||||
type: string
|
|
||||||
expires_at:
|
|
||||||
type: string
|
|
||||||
token:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
paths:
|
paths:
|
||||||
/api/v1/statistics/games/last-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns the most recently played games
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of results (default: 10)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.GameWithSongs'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get last played games
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/games/least-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns the top N least played games with their songs
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of results (default: 10)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.GameWithSongs'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get least played games
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/games/most-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns the top N most played games with their songs
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of results (default: 10)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.GameWithSongs'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get most played games
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/games/never-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns all games that have never been played (times_played = 0)
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.GameWithSongs'
|
|
||||||
type: array
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get never played games
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/games/oldest-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns the least recently played games (that have been played
|
|
||||||
at least once)
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of results (default: 10)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.GameWithSongs'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get oldest played games
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/songs/least-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns the top N least played songs with their game info
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of results (default: 10)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.SongInfoForStats'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get least played songs
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/songs/most-played:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns the top N most played songs with their game info
|
|
||||||
parameters:
|
|
||||||
- description: 'Number of results (default: 10)'
|
|
||||||
in: query
|
|
||||||
name: limit
|
|
||||||
type: integer
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.SongInfoForStats'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get most played songs
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/statistics/summary:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns overall statistics about the music library
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/backend.StatisticsSummary'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Get statistics summary
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
/api/v1/token:
|
|
||||||
delete:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Deletes the current session token
|
|
||||||
parameters:
|
|
||||||
- description: Bearer token
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"401":
|
|
||||||
description: Unauthorized
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Invalidate session token
|
|
||||||
tags:
|
|
||||||
- auth
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Returns a new session token for API access
|
|
||||||
parameters:
|
|
||||||
- description: Client type
|
|
||||||
in: body
|
|
||||||
name: request
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/server.TokenRequest'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/server.TokenResponse'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Create session token
|
|
||||||
tags:
|
|
||||||
- auth
|
|
||||||
/api/v1/token/cleanup:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Removes all expired session tokens from the database
|
|
||||||
parameters:
|
|
||||||
- description: Bearer token
|
|
||||||
in: header
|
|
||||||
name: Authorization
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
additionalProperties: true
|
|
||||||
type: object
|
|
||||||
"401":
|
|
||||||
description: Unauthorized
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Cleanup expired sessions
|
|
||||||
tags:
|
|
||||||
- auth
|
|
||||||
/character:
|
/character:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -466,6 +53,21 @@ paths:
|
|||||||
summary: Get list of characters
|
summary: Get list of characters
|
||||||
tags:
|
tags:
|
||||||
- characters
|
- characters
|
||||||
|
/dbtest:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Tests the database connection
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: TestedDB
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: Test database connection
|
||||||
|
tags:
|
||||||
|
- database
|
||||||
/download:
|
/download:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -621,7 +223,7 @@ paths:
|
|||||||
description: Syncing is in progress
|
description: Syncing is in progress
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
summary: Get all soundtracks
|
summary: Get all games
|
||||||
tags:
|
tags:
|
||||||
- music
|
- music
|
||||||
/music/all/random:
|
/music/all/random:
|
||||||
@@ -643,7 +245,7 @@ paths:
|
|||||||
description: Syncing is in progress
|
description: Syncing is in progress
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
summary: Get all soundtracks random
|
summary: Get all games random
|
||||||
tags:
|
tags:
|
||||||
- music
|
- music
|
||||||
/music/info:
|
/music/info:
|
||||||
@@ -857,14 +459,14 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Start syncing soundtracks
|
description: Start syncing games
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
"423":
|
"423":
|
||||||
description: Syncing is in progress
|
description: Syncing is in progress
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
summary: Sync soundtracks with only changes
|
summary: Sync games with only changes
|
||||||
tags:
|
tags:
|
||||||
- sync
|
- sync
|
||||||
/sync/full:
|
/sync/full:
|
||||||
@@ -876,7 +478,7 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Start syncing soundtracks full
|
description: Start syncing games full
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
"423":
|
"423":
|
||||||
@@ -911,21 +513,21 @@ paths:
|
|||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Soundtracks and songs are deleted from the database
|
description: Games and songs are deleted from the database
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
"423":
|
"423":
|
||||||
description: Syncing is in progress
|
description: Syncing is in progress
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
summary: Reset soundtracks database
|
summary: Reset games database
|
||||||
tags:
|
tags:
|
||||||
- sync
|
- sync
|
||||||
/version:
|
/version:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: get latest version info
|
description: get string by ID
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
@@ -937,28 +539,7 @@ paths:
|
|||||||
description: Not Found
|
description: Not Found
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
summary: Getting the latest version of the backend
|
summary: Getting the version of the backend
|
||||||
tags:
|
tags:
|
||||||
- version
|
- accounts
|
||||||
/version/history:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: get version history
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/backend.VersionData'
|
|
||||||
type: array
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: Getting the version history of the backend
|
|
||||||
tags:
|
|
||||||
- version
|
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
|
|||||||
@@ -1,33 +1,5 @@
|
|||||||
/* Pure CSS styles for Music Search */
|
/* Pure CSS styles for Music Search */
|
||||||
|
|
||||||
:root {
|
|
||||||
/* Light mode colors (default) */
|
|
||||||
--bg-primary: #f3f4f6;
|
|
||||||
--bg-secondary: #e5e7eb;
|
|
||||||
--bg-tertiary: #dcfce7;
|
|
||||||
--text-primary: #000;
|
|
||||||
--text-secondary: #374151;
|
|
||||||
--border-primary: #9ca3af;
|
|
||||||
--border-focus: #6b7280;
|
|
||||||
--accent-primary: #f97316;
|
|
||||||
--accent-hover: #ea580c;
|
|
||||||
--shadow-color: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] {
|
|
||||||
/* Dark mode colors matching frontend */
|
|
||||||
--bg-primary: #555;
|
|
||||||
--bg-secondary: #333;
|
|
||||||
--bg-tertiary: #2a2a2a;
|
|
||||||
--text-primary: #fff;
|
|
||||||
--text-secondary: #ff9c00;
|
|
||||||
--border-primary: #666;
|
|
||||||
--border-focus: #ff9c00;
|
|
||||||
--accent-primary: #ff9c00;
|
|
||||||
--accent-hover: #e68a00;
|
|
||||||
--shadow-color: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -38,9 +10,7 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
background-color: var(--bg-primary);
|
background-color: #f3f4f6;
|
||||||
color: var(--text-primary);
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@@ -59,15 +29,15 @@ main {
|
|||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border: 1px solid var(--border-primary);
|
border: 1px solid #9ca3af;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
background-color: var(--bg-secondary);
|
background-color: #e5e7eb;
|
||||||
color: var(--text-primary);
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search_term:focus {
|
#search_term:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--border-focus);
|
border-color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
#clear {
|
#clear {
|
||||||
@@ -75,48 +45,23 @@ main {
|
|||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
background-color: var(--accent-primary);
|
background-color: #f97316;
|
||||||
color: var(--text-primary);
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#clear:hover {
|
#clear:hover {
|
||||||
background-color: var(--accent-hover);
|
background-color: #ea580c;
|
||||||
}
|
}
|
||||||
|
|
||||||
#games-container {
|
#games-container {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-text {
|
|
||||||
color: var(--text-primary);
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode toggle */
|
|
||||||
#dark-mode-toggle {
|
|
||||||
position: fixed;
|
|
||||||
top: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding: 0.4rem 0.8rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background-color: var(--bg-secondary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 1000;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dark-mode-toggle:hover {
|
|
||||||
background-color: var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Game result cards */
|
/* Game result cards */
|
||||||
.bg-green-100 {
|
.bg-green-100 {
|
||||||
background-color: var(--bg-tertiary);
|
background-color: #dcfce7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-4 {
|
.p-4 {
|
||||||
@@ -124,7 +69,7 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.shadow-md {
|
.shadow-md {
|
||||||
box-shadow: 0 4px 6px -1px var(--shadow-color), 0 2px 4px -2px var(--shadow-color);
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-lg {
|
.rounded-lg {
|
||||||
|
|||||||
+1
-23
@@ -2,7 +2,6 @@ package web
|
|||||||
|
|
||||||
templ HelloForm() {
|
templ HelloForm() {
|
||||||
@Base() {
|
@Base() {
|
||||||
<button id="dark-mode-toggle">🌙</button>
|
|
||||||
<div id="search-container">
|
<div id="search-container">
|
||||||
<input id="search_term" name="search_term" type="text" hx-post="/find" hx-trigger="keyup changed delay:0.25s" hx-target="#games-container"/>
|
<input id="search_term" name="search_term" type="text" hx-post="/find" hx-trigger="keyup changed delay:0.25s" hx-target="#games-container"/>
|
||||||
<button type="button" id="clear" name="clear">Clear</button>
|
<button type="button" id="clear" name="clear">Clear</button>
|
||||||
@@ -13,29 +12,8 @@ templ HelloForm() {
|
|||||||
if (document.readyState == 'complete') {
|
if (document.readyState == 'complete') {
|
||||||
htmx.ajax('POST', '/find', '#games-container');
|
htmx.ajax('POST', '/find', '#games-container');
|
||||||
document.getElementById("search_term").focus();
|
document.getElementById("search_term").focus();
|
||||||
|
|
||||||
// Initialize dark mode from localStorage (default to dark)
|
|
||||||
const savedTheme = localStorage.getItem('theme') || 'dark';
|
|
||||||
if (savedTheme === 'dark') {
|
|
||||||
document.documentElement.setAttribute('data-theme', 'dark');
|
|
||||||
document.getElementById('dark-mode-toggle').textContent = '☀️';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dark mode toggle functionality
|
|
||||||
document.getElementById("dark-mode-toggle").addEventListener("click", function() {
|
|
||||||
const html = document.documentElement;
|
|
||||||
const currentTheme = html.getAttribute('data-theme');
|
|
||||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
||||||
|
|
||||||
html.setAttribute('data-theme', newTheme);
|
|
||||||
localStorage.setItem('theme', newTheme);
|
|
||||||
|
|
||||||
// Update toggle button text
|
|
||||||
this.textContent = newTheme === 'dark' ? '☀️' : '🌙';
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("clear").addEventListener("click", function (event) {
|
document.getElementById("clear").addEventListener("click", function (event) {
|
||||||
document.getElementById("search_term").value = "";
|
document.getElementById("search_term").value = "";
|
||||||
htmx.ajax('POST', '/find', '#games-container');
|
htmx.ajax('POST', '/find', '#games-container');
|
||||||
@@ -48,7 +26,7 @@ templ HelloForm() {
|
|||||||
templ FoundGames(games []string) {
|
templ FoundGames(games []string) {
|
||||||
for _, game := range games {
|
for _, game := range games {
|
||||||
<div class="bg-green-100 p-4 shadow-md rounded-lg mt-6">
|
<div class="bg-green-100 p-4 shadow-md rounded-lg mt-6">
|
||||||
<p class="game-text">{ game }</p>
|
<p>{ game }</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"music-server/internal/logging"
|
"music-server/internal/logging"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetCharacterList() []string {
|
func GetCharacterList() []string {
|
||||||
@@ -30,10 +30,10 @@ func GetCharacterList() []string {
|
|||||||
|
|
||||||
func GetCharacter(character string) string {
|
func GetCharacter(character string) string {
|
||||||
charactersPath := os.Getenv("CHARACTERS_PATH")
|
charactersPath := os.Getenv("CHARACTERS_PATH")
|
||||||
|
logging.GetLogger().Debug("Getting character", zap.String("character", character), zap.String("path", charactersPath))
|
||||||
// Clean the path - remove trailing slashes and then add one for consistency
|
// Clean the path - remove trailing slashes and then add one for consistency
|
||||||
charactersPath = strings.TrimSuffix(charactersPath, "/")
|
charactersPath = strings.TrimSuffix(charactersPath, "/")
|
||||||
charactersPath += "/"
|
charactersPath += "/"
|
||||||
logging.GetLogger().Debug("Getting character", zap.String("character", character), zap.String("path", charactersPath+character))
|
|
||||||
return charactersPath + character
|
return charactersPath + character
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"music-server/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDB() {
|
||||||
|
db.Testf()
|
||||||
|
}
|
||||||
|
|
||||||
|
type VersionData struct {
|
||||||
|
Version string `json:"version" example:"1.0.0"`
|
||||||
|
Changelog string `json:"changelog" example:"account name"`
|
||||||
|
History []VersionData `json:"history"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVersionHistory() VersionData {
|
||||||
|
data := VersionData{Version: "4.5.0",
|
||||||
|
Changelog: "#1 - Created request to check newest version of the app\n" +
|
||||||
|
"#2 - Added request to download the newest version of the app\n" +
|
||||||
|
"#3 - Added request to check progress during sync\n" +
|
||||||
|
"#4 - Now blocking all request while sync is in progress\n" +
|
||||||
|
"#5 - Implemented ants for thread pooling\n" +
|
||||||
|
"#6 - Changed the sync request to now only start the sync",
|
||||||
|
History: []VersionData{
|
||||||
|
{
|
||||||
|
Version: "4.0.0",
|
||||||
|
Changelog: "Changed framework from gin to Echo\n" +
|
||||||
|
"Reorganized the code\n" +
|
||||||
|
"Implemented sqlc\n" +
|
||||||
|
"Added support to send character images from the server\n" +
|
||||||
|
"Added function to create a new database of no one exists",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "3.2",
|
||||||
|
Changelog: "Upgraded Go version and the version of all dependencies. Fixed som more bugs.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "3.1",
|
||||||
|
Changelog: "Fixed some bugs with songs not found made the application crash. Now checking if song exists and if not, remove song from DB and find another one. Frontend is now decoupled from the backend.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "3.0",
|
||||||
|
Changelog: "Changed routing framework from mux to Gin. Swagger doc is now included in the application. A fronted can now be hosted from the application.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.3.0",
|
||||||
|
Changelog: "Images should not be included in the database, removes songs where the path doesn't work.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.2.0",
|
||||||
|
Changelog: "Changed the structure of the whole application, should be no changes to functionality.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.1.4",
|
||||||
|
Changelog: "Game list should now be sorted, a new endpoint with the game list in random order have been added.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.1.3",
|
||||||
|
Changelog: "Added a check to see if song exists before returning it, if not a new song will be picked up.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.1.2",
|
||||||
|
Changelog: "Added test server to swagger file.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.1.1",
|
||||||
|
Changelog: "Fixed bug where wrong song was showed as currently played.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.1.0",
|
||||||
|
Changelog: "Added /addQue to add the last received song to the songQue. " +
|
||||||
|
"Changed /rand and /rand/low to not add song to the que. " +
|
||||||
|
"Changed /next to not call /rand when the end of the que is reached, instead the last song in the que will be resent.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.0.3",
|
||||||
|
Changelog: "Another small change that should fix the caching problem.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.0.2",
|
||||||
|
Changelog: "Hopefully fixed the caching problem with random.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.0.1",
|
||||||
|
Changelog: "Fixed CORS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Version: "2.0.0",
|
||||||
|
Changelog: "Rebuilt the application in Go.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
|
|
||||||
// Test the average calculation logic directly without database access
|
// Test the average calculation logic directly without database access
|
||||||
func TestCalculateAverage(t *testing.T) {
|
func TestCalculateAverage(t *testing.T) {
|
||||||
games := []repository.Soundtrack{
|
games := []repository.Game{
|
||||||
{SoundtrackName: "Game1", TimesPlayed: 10},
|
{GameName: "Game1", TimesPlayed: 10},
|
||||||
{SoundtrackName: "Game2", TimesPlayed: 20},
|
{GameName: "Game2", TimesPlayed: 20},
|
||||||
{SoundtrackName: "Game3", TimesPlayed: 30},
|
{GameName: "Game3", TimesPlayed: 30},
|
||||||
}
|
}
|
||||||
|
|
||||||
var sum int32
|
var sum int32
|
||||||
@@ -28,7 +28,7 @@ func TestCalculateAverage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCalculateAverageEmpty(t *testing.T) {
|
func TestCalculateAverageEmpty(t *testing.T) {
|
||||||
games := []repository.Soundtrack{}
|
games := []repository.Game{}
|
||||||
|
|
||||||
if len(games) == 0 {
|
if len(games) == 0 {
|
||||||
result := int32(0)
|
result := int32(0)
|
||||||
@@ -52,8 +52,8 @@ func TestCalculateAverageEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCalculateAverageSingle(t *testing.T) {
|
func TestCalculateAverageSingle(t *testing.T) {
|
||||||
games := []repository.Soundtrack{
|
games := []repository.Game{
|
||||||
{SoundtrackName: "Game1", TimesPlayed: 42},
|
{GameName: "Game1", TimesPlayed: 42},
|
||||||
}
|
}
|
||||||
|
|
||||||
var sum int32
|
var sum int32
|
||||||
@@ -69,10 +69,10 @@ func TestCalculateAverageSingle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRandomGame(t *testing.T) {
|
func TestGetRandomGame(t *testing.T) {
|
||||||
games := []repository.Soundtrack{
|
games := []repository.Game{
|
||||||
{SoundtrackName: "Game1", TimesPlayed: 10},
|
{GameName: "Game1", TimesPlayed: 10},
|
||||||
{SoundtrackName: "Game2", TimesPlayed: 20},
|
{GameName: "Game2", TimesPlayed: 20},
|
||||||
{SoundtrackName: "Game3", TimesPlayed: 30},
|
{GameName: "Game3", TimesPlayed: 30},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set seed for reproducible tests
|
// Set seed for reproducible tests
|
||||||
@@ -80,93 +80,93 @@ func TestGetRandomGame(t *testing.T) {
|
|||||||
|
|
||||||
result := games[rand.Intn(len(games))]
|
result := games[rand.Intn(len(games))]
|
||||||
|
|
||||||
if result.SoundtrackName == "" {
|
if result.GameName == "" {
|
||||||
t.Error("random game selection returned empty game")
|
t.Error("random game selection returned empty game")
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, g := range games {
|
for _, g := range games {
|
||||||
if g.SoundtrackName == result.SoundtrackName {
|
if g.GameName == result.GameName {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("random game selection returned game not in list: %v", result.SoundtrackName)
|
t.Errorf("random game selection returned game not in list: %v", result.GameName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindGameByID(t *testing.T) {
|
func TestFindGameByID(t *testing.T) {
|
||||||
games := []repository.Soundtrack{
|
games := []repository.Game{
|
||||||
{ID: 1, SoundtrackName: "Game1", TimesPlayed: 10},
|
{ID: 1, GameName: "Game1", TimesPlayed: 10},
|
||||||
{ID: 2, SoundtrackName: "Game2", TimesPlayed: 20},
|
{ID: 2, GameName: "Game2", TimesPlayed: 20},
|
||||||
{ID: 3, SoundtrackName: "Game3", TimesPlayed: 30},
|
{ID: 3, GameName: "Game3", TimesPlayed: 30},
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
games []repository.Soundtrack
|
games []repository.Game
|
||||||
gameID int32
|
gameID int32
|
||||||
expected repository.Soundtrack
|
expected repository.Game
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "existing game",
|
name: "existing game",
|
||||||
games: games,
|
games: games,
|
||||||
gameID: 2,
|
gameID: 2,
|
||||||
expected: repository.Soundtrack{ID: 2, SoundtrackName: "Game2", TimesPlayed: 20},
|
expected: repository.Game{ID: 2, GameName: "Game2", TimesPlayed: 20},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-existing game",
|
name: "non-existing game",
|
||||||
games: games,
|
games: games,
|
||||||
gameID: 99,
|
gameID: 99,
|
||||||
expected: repository.Soundtrack{},
|
expected: repository.Game{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var result repository.Soundtrack
|
var result repository.Game
|
||||||
for _, game := range tt.games {
|
for _, game := range tt.games {
|
||||||
if game.ID == tt.gameID {
|
if game.ID == tt.gameID {
|
||||||
result = game
|
result = game
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result.ID != tt.expected.ID || result.SoundtrackName != tt.expected.SoundtrackName {
|
if result.ID != tt.expected.ID || result.GameName != tt.expected.GameName {
|
||||||
t.Errorf("findGameByID() = %v, want %v", result, tt.expected)
|
t.Errorf("findGameByID() = %v, want %v", result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractSoundtrackNames(t *testing.T) {
|
func TestExtractGameNames(t *testing.T) {
|
||||||
games := []repository.Soundtrack{
|
games := []repository.Game{
|
||||||
{SoundtrackName: "Game1", TimesPlayed: 10},
|
{GameName: "Game1", TimesPlayed: 10},
|
||||||
{SoundtrackName: "Game2", TimesPlayed: 20},
|
{GameName: "Game2", TimesPlayed: 20},
|
||||||
{SoundtrackName: "Game3", TimesPlayed: 30},
|
{GameName: "Game3", TimesPlayed: 30},
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for _, game := range games {
|
for _, game := range games {
|
||||||
result = append(result, game.SoundtrackName)
|
result = append(result, game.GameName)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []string{"Game1", "Game2", "Game3"}
|
expected := []string{"Game1", "Game2", "Game3"}
|
||||||
|
|
||||||
if len(result) != len(expected) {
|
if len(result) != len(expected) {
|
||||||
t.Errorf("extractSoundtrackNames() length = %d, want %d", len(result), len(expected))
|
t.Errorf("extractGameNames() length = %d, want %d", len(result), len(expected))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range result {
|
for i, v := range result {
|
||||||
if v != expected[i] {
|
if v != expected[i] {
|
||||||
t.Errorf("extractSoundtrackNames()[%d] = %v, want %v", i, v, expected[i])
|
t.Errorf("extractGameNames()[%d] = %v, want %v", i, v, expected[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShuffleSoundtrackNames(t *testing.T) {
|
func TestShuffleGameNames(t *testing.T) {
|
||||||
games := []string{"Game1", "Game2", "Game3"}
|
games := []string{"Game1", "Game2", "Game3"}
|
||||||
|
|
||||||
// Test that shuffle doesn't lose any elements
|
// Test that shuffle doesn't lose any elements
|
||||||
@@ -181,7 +181,7 @@ func TestShuffleSoundtrackNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(games) != len(original) {
|
if len(games) != len(original) {
|
||||||
t.Errorf("shuffleSoundtrackNames() changed length from %d to %d", len(original), len(games))
|
t.Errorf("shuffleGameNames() changed length from %d to %d", len(original), len(games))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,9 @@ func TestShuffleSoundtrackNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("shuffleSoundtrackNames() lost element: %v", orig)
|
t.Errorf("shuffleGameNames() lost element: %v", orig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ func SyncSoundtracksNewOnlyChanges() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func syncGamesNew(full bool) {
|
func syncGamesNew(full bool) {
|
||||||
|
Syncing = true
|
||||||
|
|
||||||
musicPath := os.Getenv("MUSIC_PATH")
|
musicPath := os.Getenv("MUSIC_PATH")
|
||||||
fmt.Printf("dir: %s\n", musicPath)
|
fmt.Printf("dir: %s\n", musicPath)
|
||||||
logging.GetLogger().Debug("Folder to sync", zap.String("MUSIC_PATH", musicPath))
|
logging.GetLogger().Debug("Folder to sync", zap.String("MUSIC_PATH", musicPath))
|
||||||
@@ -197,7 +199,7 @@ func syncGamesNew(full bool) {
|
|||||||
|
|
||||||
initRepo()
|
initRepo()
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
foldersToSkip := []string{".sync", "characters", "dist", "old"}
|
foldersToSkip := []string{".sync", "dist", "old", "characters"}
|
||||||
logging.GetLogger().Debug("Folders to skip during sync", zap.Strings("folders", foldersToSkip))
|
logging.GetLogger().Debug("Folders to skip during sync", zap.Strings("folders", foldersToSkip))
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -222,6 +224,7 @@ func syncGamesNew(full bool) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logging.GetLogger().Fatal("Failed to read music directory", zap.String("path", musicPath), zap.String("error", err.Error()))
|
logging.GetLogger().Fatal("Failed to read music directory", zap.String("path", musicPath), zap.String("error", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pool, _ = ants.NewPool(10, ants.WithPreAlloc(true))
|
pool, _ = ants.NewPool(10, ants.WithPreAlloc(true))
|
||||||
poolSong, _ = ants.NewPool(10, ants.WithPreAlloc(true))
|
poolSong, _ = ants.NewPool(10, ants.WithPreAlloc(true))
|
||||||
defer pool.Release()
|
defer pool.Release()
|
||||||
@@ -319,7 +322,7 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if full && status != NewGame {
|
if full {
|
||||||
status = TitleChanged
|
status = TitleChanged
|
||||||
}
|
}
|
||||||
entries, err := os.ReadDir(gameDir)
|
entries, err := os.ReadDir(gameDir)
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
package backend
|
|
||||||
|
|
||||||
type VersionData struct {
|
|
||||||
Version string `json:"version" example:"1.0.0"`
|
|
||||||
Changelog []string `json:"changelog" example:"[\"Initial release\",\"Bug fixes\"]"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = []VersionData{
|
|
||||||
{
|
|
||||||
Version: "5.0.0-Beta",
|
|
||||||
Changelog: []string{
|
|
||||||
"#16 - Upgrade Echo framework from v4 to v5",
|
|
||||||
"#17 - Add Zap structured logging framework",
|
|
||||||
"#18 - Add OpenAPI/Swagger documentation",
|
|
||||||
"#19 - Replace Tailwind CSS with pure CSS",
|
|
||||||
"#20 - Change domain from sanplex.tech to sanplex.xyz",
|
|
||||||
"#21 - Refactor handlers into domain-specific files",
|
|
||||||
"#22 - Change VersionData Changelog from string to string array",
|
|
||||||
"#23 - Update all dependencies to latest versions",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "4.5.0",
|
|
||||||
Changelog: []string{
|
|
||||||
"#1 - Created request to check newest version of the app",
|
|
||||||
"#2 - Added request to download the newest version of the app",
|
|
||||||
"#3 - Added request to check progress during sync",
|
|
||||||
"#4 - Now blocking all request while sync is in progress",
|
|
||||||
"#5 - Implemented ants for thread pooling",
|
|
||||||
"#6 - Changed the sync request to now only start the sync",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "4.0.0",
|
|
||||||
Changelog: []string{
|
|
||||||
"Changed framework from gin to Echo",
|
|
||||||
"Reorganized the code",
|
|
||||||
"Implemented sqlc",
|
|
||||||
"Added support to send character images from the server",
|
|
||||||
"Added function to create a new database of no one exists",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "3.2",
|
|
||||||
Changelog: []string{"Upgraded Go version and the version of all dependencies. Fixed som more bugs."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "3.1",
|
|
||||||
Changelog: []string{"Fixed some bugs with songs not found made the application crash. Now checking if song exists and if not, remove song from DB and find another one. Frontend is now decoupled from the backend."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "3.0",
|
|
||||||
Changelog: []string{"Changed routing framework from mux to Gin. Swagger doc is now included in the application. A fronted can now be hosted from the application."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.3.0",
|
|
||||||
Changelog: []string{"Images should not be included in the database, removes songs where the path doesn't work."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.2.0",
|
|
||||||
Changelog: []string{"Changed the structure of the whole application, should be no changes to functionality."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.1.4",
|
|
||||||
Changelog: []string{"Game list should now be sorted, a new endpoint with the game list in random order have been added."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.1.3",
|
|
||||||
Changelog: []string{"Added a check to see if song exists before returning it, if not a new song will be picked up."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.1.2",
|
|
||||||
Changelog: []string{"Added test server to swagger file."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.1.1",
|
|
||||||
Changelog: []string{"Fixed bug where wrong song was showed as currently played."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.1.0",
|
|
||||||
Changelog: []string{
|
|
||||||
"Added /addQue to add the last received song to the songQue.",
|
|
||||||
"Changed /rand and /rand/low to not add song to the que.",
|
|
||||||
"Changed /next to not call /rand when the end of the que is reached, instead the last song in the que will be resent.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.0.3",
|
|
||||||
Changelog: []string{"Another small change that should fix the caching problem."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.0.2",
|
|
||||||
Changelog: []string{"Hopefully fixed the caching problem with random."},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.0.1",
|
|
||||||
Changelog: []string{"Fixed CORS"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: "2.0.0",
|
|
||||||
Changelog: []string{"Rebuilt the application in Go."},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLatestVersion() VersionData {
|
|
||||||
return data[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetVersionHistory() []VersionData {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"music-server/internal/logging"
|
"music-server/internal/logging"
|
||||||
|
|
||||||
@@ -60,26 +59,6 @@ func (db *Database) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health checks the health of the database connection by pinging the database.
|
|
||||||
// It returns a map with keys indicating various health statistics.
|
|
||||||
func (db *Database) Health() map[string]string {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
stats := make(map[string]string)
|
|
||||||
|
|
||||||
// Ping the database
|
|
||||||
err := db.Pool.Ping(ctx)
|
|
||||||
if err != nil {
|
|
||||||
stats["status"] = "down"
|
|
||||||
stats["error"] = err.Error()
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
|
|
||||||
stats["status"] = "up"
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunMigrations runs all pending database migrations to the latest version.
|
// RunMigrations runs all pending database migrations to the latest version.
|
||||||
// Uses the existing pool to extract connection details.
|
// Uses the existing pool to extract connection details.
|
||||||
func (db *Database) RunMigrations() error {
|
func (db *Database) RunMigrations() error {
|
||||||
|
|||||||
@@ -56,6 +56,21 @@ func CloseDb() {
|
|||||||
Dbpool.Close()
|
Dbpool.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Testf() {
|
||||||
|
rows, dbErr := Dbpool.Query(Ctx, "select game_name from game")
|
||||||
|
if dbErr != nil {
|
||||||
|
logging.GetLogger().Fatal("Query failed", zap.String("error", dbErr.Error()))
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var gameName string
|
||||||
|
dbErr = rows.Scan(&gameName)
|
||||||
|
if dbErr != nil {
|
||||||
|
logging.GetLogger().Error("Row scan failed", zap.String("error", dbErr.Error()))
|
||||||
|
}
|
||||||
|
logging.GetLogger().Debug("Game found", zap.String("name", gameName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ResetGameIdSeq() {
|
func ResetGameIdSeq() {
|
||||||
_, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
|
_, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -79,9 +80,9 @@ func TestMigrationsStepByStep(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range songs {
|
for _, s := range songs {
|
||||||
_, err := db.Exec(`INSERT INTO song (game_id, song_name, path, hash)
|
_, err := db.Exec(`INSERT INTO song (game_id, song_name, path)
|
||||||
VALUES ($1, $2, $3, $4)`,
|
VALUES ($1, $2, $3)`,
|
||||||
s.gameID, s.name, s.path, fmt.Sprintf("song-hash-%s", s.name))
|
s.gameID, s.name, s.path)
|
||||||
require.NoError(t, err, "Failed to insert song %s", s.name)
|
require.NoError(t, err, "Failed to insert song %s", s.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,9 +95,9 @@ func TestMigrationsStepByStep(t *testing.T) {
|
|||||||
var songCount int
|
var songCount int
|
||||||
err = db.QueryRow("SELECT COUNT(*) FROM song").Scan(&songCount)
|
err = db.QueryRow("SELECT COUNT(*) FROM song").Scan(&songCount)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 9, songCount, "Expected 9 songs")
|
require.Equal(t, 8, songCount, "Expected 8 songs")
|
||||||
|
|
||||||
t.Log("✓ Manually inserted 5 games with 9 songs")
|
t.Log("✓ Manually inserted 5 games with 8 songs")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Step 3: Apply migration 5 (rename game→soundtrack)
|
// Step 3: Apply migration 5 (rename game→soundtrack)
|
||||||
@@ -125,7 +126,7 @@ func TestMigrationsStepByStep(t *testing.T) {
|
|||||||
var songCount int
|
var songCount int
|
||||||
err = db.QueryRow("SELECT COUNT(*) FROM song").Scan(&songCount)
|
err = db.QueryRow("SELECT COUNT(*) FROM song").Scan(&songCount)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 9, songCount, "Expected 9 songs after migration")
|
require.Equal(t, 8, songCount, "Expected 8 songs after migration")
|
||||||
|
|
||||||
// Verify data integrity: soundtrack_name values
|
// Verify data integrity: soundtrack_name values
|
||||||
rows, err := db.Query("SELECT soundtrack_name FROM soundtrack ORDER BY id")
|
rows, err := db.Query("SELECT soundtrack_name FROM soundtrack ORDER BY id")
|
||||||
@@ -214,18 +215,13 @@ func applyMigrations(t *testing.T, host, port, user, password, dbname string, st
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
m, err := migrate.NewWithDatabaseInstance(
|
m, err := migrate.NewWithDatabaseInstance(
|
||||||
"file://migrations",
|
"file://internal/db/migrations",
|
||||||
"postgres", driver)
|
"postgres", driver)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Get current version
|
// Get current version
|
||||||
version, _, err := m.Version()
|
version, _, err := m.Version()
|
||||||
if err != nil && err != migrate.ErrNilVersion {
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
if err == migrate.ErrNilVersion {
|
|
||||||
version = 0
|
|
||||||
}
|
|
||||||
t.Logf("Current migration version: %d", version)
|
t.Logf("Current migration version: %d", version)
|
||||||
|
|
||||||
// Apply exactly 'steps' migrations
|
// Apply exactly 'steps' migrations
|
||||||
@@ -241,11 +237,6 @@ func applyMigrations(t *testing.T, host, port, user, password, dbname string, st
|
|||||||
|
|
||||||
// Get new version
|
// Get new version
|
||||||
newVersion, _, err := m.Version()
|
newVersion, _, err := m.Version()
|
||||||
if err != nil && err != migrate.ErrNilVersion {
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
if err == migrate.ErrNilVersion {
|
|
||||||
newVersion = 0
|
|
||||||
}
|
|
||||||
t.Logf("Migration version after applying %d steps: %d", steps, newVersion)
|
t.Logf("Migration version after applying %d steps: %d", steps, newVersion)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ ALTER TABLE song RENAME COLUMN game_id TO soundtrack_id;
|
|||||||
-- Update song primary key
|
-- Update song primary key
|
||||||
ALTER TABLE song DROP CONSTRAINT IF EXISTS song_pkey;
|
ALTER TABLE song DROP CONSTRAINT IF EXISTS song_pkey;
|
||||||
ALTER TABLE song ADD PRIMARY KEY (soundtrack_id, path);
|
ALTER TABLE song ADD PRIMARY KEY (soundtrack_id, path);
|
||||||
|
ALTER TABLE song RENAME CONSTRAINT song_pkey TO song_pkey_soundtrack;
|
||||||
|
|
||||||
-- Update song_list table references
|
-- Update song_list table references
|
||||||
ALTER TABLE song_list RENAME COLUMN game_name TO soundtrack_name;
|
ALTER TABLE song_list RENAME COLUMN game_name TO soundtrack_name;
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ LIMIT $1;
|
|||||||
-- name: GetStatisticsSummary :one
|
-- name: GetStatisticsSummary :one
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) as total_soundtracks,
|
COUNT(*) as total_soundtracks,
|
||||||
COALESCE(SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END), 0)::bigint as played_soundtracks,
|
SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END) as played_soundtracks,
|
||||||
COALESCE(SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END), 0)::bigint as never_played_soundtracks,
|
SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END) as never_played_soundtracks,
|
||||||
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
|
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
|
||||||
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
|
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
|
||||||
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
|
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type IDMigrationStatus struct {
|
||||||
|
TableName string `json:"table_name"`
|
||||||
|
TotalRows int32 `json:"total_rows"`
|
||||||
|
MigratedRows int32 `json:"migrated_rows"`
|
||||||
|
Completed bool `json:"completed"`
|
||||||
|
StartedAt *time.Time `json:"started_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
IpAddress string `json:"ip_address"`
|
IpAddress string `json:"ip_address"`
|
||||||
@@ -27,6 +35,7 @@ type Song struct {
|
|||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
FileName *string `json:"file_name"`
|
FileName *string `json:"file_name"`
|
||||||
ID pgtype.Int4 `json:"id"`
|
ID pgtype.Int4 `json:"id"`
|
||||||
|
Uuid pgtype.UUID `json:"uuid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SongList struct {
|
type SongList struct {
|
||||||
@@ -48,6 +57,7 @@ type Soundtrack struct {
|
|||||||
LastPlayed *time.Time `json:"last_played"`
|
LastPlayed *time.Time `json:"last_played"`
|
||||||
NumberOfSongs int32 `json:"number_of_songs"`
|
NumberOfSongs int32 `json:"number_of_songs"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
|
Uuid pgtype.UUID `json:"uuid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vgmq struct {
|
type Vgmq struct {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func (q *Queries) ClearSongsBySoundtrackId(ctx context.Context, soundtrackID int
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllSongs = `-- name: FetchAllSongs :many
|
const fetchAllSongs = `-- name: FetchAllSongs :many
|
||||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id FROM song
|
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid FROM song
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
||||||
@@ -130,6 +130,7 @@ func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
|||||||
&i.Hash,
|
&i.Hash,
|
||||||
&i.FileName,
|
&i.FileName,
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.Uuid,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -142,7 +143,7 @@ func (q *Queries) FetchAllSongs(ctx context.Context) ([]Song, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const findSongsFromSoundtrack = `-- name: FindSongsFromSoundtrack :many
|
const findSongsFromSoundtrack = `-- name: FindSongsFromSoundtrack :many
|
||||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id
|
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid
|
||||||
FROM song
|
FROM song
|
||||||
WHERE soundtrack_id = $1
|
WHERE soundtrack_id = $1
|
||||||
`
|
`
|
||||||
@@ -164,6 +165,7 @@ func (q *Queries) FindSongsFromSoundtrack(ctx context.Context, soundtrackID int3
|
|||||||
&i.Hash,
|
&i.Hash,
|
||||||
&i.FileName,
|
&i.FileName,
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.Uuid,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -176,7 +178,7 @@ func (q *Queries) FindSongsFromSoundtrack(ctx context.Context, soundtrackID int3
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSongById = `-- name: GetSongById :one
|
const getSongById = `-- name: GetSongById :one
|
||||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id FROM song WHERE id = $1
|
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid FROM song WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetSongById(ctx context.Context, id pgtype.Int4) (Song, error) {
|
func (q *Queries) GetSongById(ctx context.Context, id pgtype.Int4) (Song, error) {
|
||||||
@@ -190,12 +192,13 @@ func (q *Queries) GetSongById(ctx context.Context, id pgtype.Int4) (Song, error)
|
|||||||
&i.Hash,
|
&i.Hash,
|
||||||
&i.FileName,
|
&i.FileName,
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.Uuid,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSongWithHash = `-- name: GetSongWithHash :one
|
const getSongWithHash = `-- name: GetSongWithHash :one
|
||||||
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id FROM song WHERE hash = $1
|
SELECT soundtrack_id, song_name, path, times_played, hash, file_name, id, uuid FROM song WHERE hash = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetSongWithHash(ctx context.Context, hash string) (Song, error) {
|
func (q *Queries) GetSongWithHash(ctx context.Context, hash string) (Song, error) {
|
||||||
@@ -209,6 +212,7 @@ func (q *Queries) GetSongWithHash(ctx context.Context, hash string) (Song, error
|
|||||||
&i.Hash,
|
&i.Hash,
|
||||||
&i.FileName,
|
&i.FileName,
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
&i.Uuid,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (q *Queries) ClearSoundtracks(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const findAllSoundtracks = `-- name: FindAllSoundtracks :many
|
const findAllSoundtracks = `-- name: FindAllSoundtracks :many
|
||||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid
|
||||||
FROM soundtrack
|
FROM soundtrack
|
||||||
WHERE deleted IS NULL
|
WHERE deleted IS NULL
|
||||||
ORDER BY soundtrack_name
|
ORDER BY soundtrack_name
|
||||||
@@ -54,6 +54,7 @@ func (q *Queries) FindAllSoundtracks(ctx context.Context) ([]Soundtrack, error)
|
|||||||
&i.LastPlayed,
|
&i.LastPlayed,
|
||||||
&i.NumberOfSongs,
|
&i.NumberOfSongs,
|
||||||
&i.Hash,
|
&i.Hash,
|
||||||
|
&i.Uuid,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ func (q *Queries) FindAllSoundtracks(ctx context.Context) ([]Soundtrack, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAllSoundtracksIncludingDeleted = `-- name: GetAllSoundtracksIncludingDeleted :many
|
const getAllSoundtracksIncludingDeleted = `-- name: GetAllSoundtracksIncludingDeleted :many
|
||||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid
|
||||||
FROM soundtrack
|
FROM soundtrack
|
||||||
ORDER BY soundtrack_name
|
ORDER BY soundtrack_name
|
||||||
`
|
`
|
||||||
@@ -91,6 +92,7 @@ func (q *Queries) GetAllSoundtracksIncludingDeleted(ctx context.Context) ([]Soun
|
|||||||
&i.LastPlayed,
|
&i.LastPlayed,
|
||||||
&i.NumberOfSongs,
|
&i.NumberOfSongs,
|
||||||
&i.Hash,
|
&i.Hash,
|
||||||
|
&i.Uuid,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -114,7 +116,7 @@ func (q *Queries) GetIdBySoundtrackName(ctx context.Context, soundtrackName stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSoundtrackById = `-- name: GetSoundtrackById :one
|
const getSoundtrackById = `-- name: GetSoundtrackById :one
|
||||||
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash
|
SELECT id, soundtrack_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs, hash, uuid
|
||||||
FROM soundtrack
|
FROM soundtrack
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
AND deleted IS NULL
|
AND deleted IS NULL
|
||||||
@@ -134,6 +136,7 @@ func (q *Queries) GetSoundtrackById(ctx context.Context, id int32) (Soundtrack,
|
|||||||
&i.LastPlayed,
|
&i.LastPlayed,
|
||||||
&i.NumberOfSongs,
|
&i.NumberOfSongs,
|
||||||
&i.Hash,
|
&i.Hash,
|
||||||
|
&i.Uuid,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,8 +398,8 @@ func (q *Queries) GetOldestPlayedGames(ctx context.Context, limit int32) ([]GetO
|
|||||||
const getStatisticsSummary = `-- name: GetStatisticsSummary :one
|
const getStatisticsSummary = `-- name: GetStatisticsSummary :one
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*) as total_soundtracks,
|
COUNT(*) as total_soundtracks,
|
||||||
COALESCE(SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END), 0)::bigint as played_soundtracks,
|
SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END) as played_soundtracks,
|
||||||
COALESCE(SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END), 0)::bigint as never_played_soundtracks,
|
SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END) as never_played_soundtracks,
|
||||||
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
|
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
|
||||||
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
|
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
|
||||||
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
|
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
|
||||||
|
|||||||
@@ -54,19 +54,8 @@ func TestSetupDB(t *testing.T) {
|
|||||||
t.Fatalf("Failed to initialize test database: %v", err)
|
t.Fatalf("Failed to initialize test database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any existing schema to ensure clean state
|
|
||||||
ctx := context.Background()
|
|
||||||
_, err = TestDatabase.Pool.Exec(ctx, "DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;")
|
|
||||||
if err != nil {
|
|
||||||
t.Logf("Warning: Could not clean schema: %v", err)
|
|
||||||
// Continue anyway, migrations might still work
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migrations
|
// Run migrations
|
||||||
if err := TestDatabase.RunMigrations(); err != nil {
|
if err := TestDatabase.RunMigrations(); err != nil {
|
||||||
// Clean up on failure to prevent nil pointer issues in other tests
|
|
||||||
TestDatabase.Close()
|
|
||||||
TestDatabase = nil
|
|
||||||
t.Fatalf("Failed to run migrations: %v", err)
|
t.Fatalf("Failed to run migrations: %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -108,11 +97,10 @@ func createTestDatabase(host, port, dbname, user, password string) {
|
|||||||
// "closed pool" errors when tests run sequentially
|
// "closed pool" errors when tests run sequentially
|
||||||
func TestTearDownDB(t *testing.T) {
|
func TestTearDownDB(t *testing.T) {
|
||||||
// CloseDb() // Disabled to prevent pool closure between sequential tests
|
// CloseDb() // Disabled to prevent pool closure between sequential tests
|
||||||
// Note: We also don't nil TestDatabase to allow reuse across tests
|
if TestDatabase != nil {
|
||||||
// if TestDatabase != nil {
|
TestDatabase.Close()
|
||||||
// TestDatabase.Close()
|
TestDatabase = nil
|
||||||
// TestDatabase = nil
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestClearDatabase clears all data from the test database
|
// TestClearDatabase clears all data from the test database
|
||||||
@@ -124,13 +112,10 @@ func TestClearDatabase(t *testing.T) {
|
|||||||
|
|
||||||
// Clear all tables in reverse order to respect foreign keys
|
// Clear all tables in reverse order to respect foreign keys
|
||||||
// Note: This assumes the tables exist and have the expected structure
|
// Note: This assumes the tables exist and have the expected structure
|
||||||
// After migration 000005, game table was renamed to soundtrack
|
|
||||||
tables := []string{
|
tables := []string{
|
||||||
"song_list",
|
"song_list",
|
||||||
"song",
|
"song",
|
||||||
"soundtrack",
|
"game",
|
||||||
"vgmq",
|
|
||||||
"sessions",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -141,10 +126,9 @@ func TestClearDatabase(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset sequences (renamed from game_id_seq to soundtrack_id_seq in migration 000005)
|
// Reset sequences
|
||||||
var seqErr error
|
_, err := TestDatabase.Pool.Exec(ctx, "SELECT setval('game_id_seq', 1, false)")
|
||||||
_, seqErr = TestDatabase.Pool.Exec(ctx, "SELECT setval('soundtrack_id_seq', 1, false)")
|
if err != nil {
|
||||||
if seqErr != nil {
|
t.Logf("Failed to reset game_id_seq: %v", err)
|
||||||
t.Logf("Failed to reset soundtrack_id_seq: %v", seqErr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v5"
|
|
||||||
"music-server/internal/backend"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CharacterHandler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCharacterHandler() *CharacterHandler {
|
|
||||||
return &CharacterHandler{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCharacterList godoc
|
|
||||||
// @Summary Get list of characters
|
|
||||||
// @Description Returns a list of all available characters
|
|
||||||
// @Tags characters
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {array} string
|
|
||||||
// @Router /characters [get]
|
|
||||||
func (c *CharacterHandler) GetCharacterList(ctx *echo.Context) error {
|
|
||||||
characters := backend.GetCharacterList()
|
|
||||||
return ctx.JSON(http.StatusOK, characters)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCharacter godoc
|
|
||||||
// @Summary Get character image
|
|
||||||
// @Description Returns the image for a specific character
|
|
||||||
// @Tags characters
|
|
||||||
// @Accept json
|
|
||||||
// @Produce image/png
|
|
||||||
// @Param name query string true "Character name"
|
|
||||||
// @Success 200 {file} file
|
|
||||||
// @Router /character [get]
|
|
||||||
func (c *CharacterHandler) GetCharacter(ctx *echo.Context) error {
|
|
||||||
character := ctx.QueryParam("name")
|
|
||||||
characterPath := backend.GetCharacter(character)
|
|
||||||
file, err := os.Open(characterPath)
|
|
||||||
if err != nil {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, err.Error())
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
return ctx.Stream(http.StatusOK, "image/png", file)
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v5"
|
|
||||||
"music-server/internal/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HealthHandler struct {
|
|
||||||
db *db.Database
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHealthHandler(database *db.Database) *HealthHandler {
|
|
||||||
return &HealthHandler{db: database}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheck godoc
|
|
||||||
//
|
|
||||||
// @Summary Check server health
|
|
||||||
// @Description Returns the health status of the server
|
|
||||||
// @Tags health
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {string} string "OK"
|
|
||||||
// @Router /health [get]
|
|
||||||
func (h *HealthHandler) HealthCheck(ctx *echo.Context) error {
|
|
||||||
return ctx.JSON(http.StatusOK, h.db.Health())
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestHealthCheck verifies the health endpoint returns database status
|
|
||||||
func TestHealthCheck(t *testing.T) {
|
|
||||||
e := StartTestServer(t)
|
|
||||||
// No explicit teardown - handled by StartTestServer's sync.Once
|
|
||||||
|
|
||||||
resp := MakeTestRequest(t, e, "GET", "/health")
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
|
|
||||||
var healthData map[string]string
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &healthData)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, healthData)
|
|
||||||
assert.Equal(t, "up", healthData["status"])
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"music-server/internal/backend"
|
||||||
|
"music-server/internal/db"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IndexHandler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIndexHandler() *IndexHandler {
|
||||||
|
return &IndexHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion godoc
|
||||||
|
//
|
||||||
|
// @Summary Getting the version of the backend
|
||||||
|
// @Description get string by ID
|
||||||
|
// @Tags accounts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} backend.VersionData
|
||||||
|
// @Failure 404 {object} string
|
||||||
|
// @Router /version [get]
|
||||||
|
func (i *IndexHandler) GetVersion(ctx *echo.Context) error {
|
||||||
|
versionHistory := backend.GetVersionHistory()
|
||||||
|
if versionHistory.Version == "" {
|
||||||
|
return ctx.JSON(http.StatusNotFound, "version not found")
|
||||||
|
}
|
||||||
|
return ctx.JSON(http.StatusOK, versionHistory)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDBTest godoc
|
||||||
|
// @Summary Test database connection
|
||||||
|
// @Description Tests the database connection
|
||||||
|
// @Tags database
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {string} string "TestedDB"
|
||||||
|
// @Router /dbtest [get]
|
||||||
|
func (i *IndexHandler) GetDBTest(ctx *echo.Context) error {
|
||||||
|
backend.TestDB()
|
||||||
|
return ctx.JSON(http.StatusOK, "TestedDB")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheck godoc
|
||||||
|
// @Summary Check server health
|
||||||
|
// @Description Returns the health status of the server
|
||||||
|
// @Tags health
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {string} string "OK"
|
||||||
|
// @Router /health [get]
|
||||||
|
func (i *IndexHandler) HealthCheck(ctx *echo.Context) error {
|
||||||
|
return ctx.JSON(http.StatusOK, db.Health())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCharacterList godoc
|
||||||
|
// @Summary Get list of characters
|
||||||
|
// @Description Returns a list of all available characters
|
||||||
|
// @Tags characters
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} string
|
||||||
|
// @Router /characters [get]
|
||||||
|
func (i *IndexHandler) GetCharacterList(ctx *echo.Context) error {
|
||||||
|
characters := backend.GetCharacterList()
|
||||||
|
return ctx.JSON(http.StatusOK, characters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCharacter godoc
|
||||||
|
// @Summary Get character image
|
||||||
|
// @Description Returns the image for a specific character
|
||||||
|
// @Tags characters
|
||||||
|
// @Accept json
|
||||||
|
// @Produce image/png
|
||||||
|
// @Param name query string true "Character name"
|
||||||
|
// @Success 200 {file} file
|
||||||
|
// @Router /character [get]
|
||||||
|
func (i *IndexHandler) GetCharacter(ctx *echo.Context) error {
|
||||||
|
character := ctx.QueryParam("name")
|
||||||
|
return ctx.File(backend.GetCharacter(character))
|
||||||
|
}
|
||||||
@@ -5,9 +5,45 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"music-server/internal/backend"
|
||||||
|
"music-server/internal/db"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestHealthCheck verifies the health endpoint returns database status
|
||||||
|
func TestHealthCheck(t *testing.T) {
|
||||||
|
// Setup database
|
||||||
|
db.TestSetupDB(t)
|
||||||
|
defer db.TestTearDownDB(t)
|
||||||
|
|
||||||
|
e := StartTestServer(t)
|
||||||
|
|
||||||
|
resp := MakeTestRequest(t, e, "GET", "/health")
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
var healthData map[string]string
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &healthData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, healthData)
|
||||||
|
assert.Equal(t, "up", healthData["status"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetVersion verifies the version endpoint returns version history
|
||||||
|
func TestGetVersion(t *testing.T) {
|
||||||
|
e := StartTestServer(t)
|
||||||
|
|
||||||
|
resp := MakeTestRequest(t, e, "GET", "/version")
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
var versionData backend.VersionData
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &versionData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, versionData.Version)
|
||||||
|
assert.NotEmpty(t, versionData.Changelog)
|
||||||
|
assert.NotEmpty(t, versionData.History)
|
||||||
|
}
|
||||||
|
|
||||||
// TestGetCharacterList verifies the characters endpoint returns list of characters
|
// TestGetCharacterList verifies the characters endpoint returns list of characters
|
||||||
func TestGetCharacterList(t *testing.T) {
|
func TestGetCharacterList(t *testing.T) {
|
||||||
e := StartTestServer(t)
|
e := StartTestServer(t)
|
||||||
@@ -45,3 +81,16 @@ func TestGetCharacterNotFound(t *testing.T) {
|
|||||||
// Should return 404 or similar error
|
// Should return 404 or similar error
|
||||||
assert.NotEqual(t, http.StatusOK, resp.Code)
|
assert.NotEqual(t, http.StatusOK, resp.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDBTest verifies the database test endpoint
|
||||||
|
func TestDBTest(t *testing.T) {
|
||||||
|
// Setup database
|
||||||
|
db.TestSetupDB(t)
|
||||||
|
defer db.TestTearDownDB(t)
|
||||||
|
|
||||||
|
e := StartTestServer(t)
|
||||||
|
|
||||||
|
resp := MakeTestRequest(t, e, "GET", "/dbtest")
|
||||||
|
assert.Equal(t, http.StatusOK, resp.Code)
|
||||||
|
assert.Contains(t, resp.Body.String(), "TestedDB")
|
||||||
|
}
|
||||||
@@ -63,16 +63,12 @@ func (s *Server) RegisterRoutes() http.Handler {
|
|||||||
// ============================================
|
// ============================================
|
||||||
deprecatedMiddleware := middleware.DeprecationMiddleware
|
deprecatedMiddleware := middleware.DeprecationMiddleware
|
||||||
|
|
||||||
health := NewHealthHandler(s.db)
|
index := NewIndexHandler()
|
||||||
e.GET("/health", deprecatedMiddleware(health.HealthCheck))
|
e.GET("/version", deprecatedMiddleware(index.GetVersion))
|
||||||
|
e.GET("/dbtest", deprecatedMiddleware(index.GetDBTest))
|
||||||
version := NewVersionHandler()
|
e.GET("/health", deprecatedMiddleware(index.HealthCheck))
|
||||||
e.GET("/version", deprecatedMiddleware(version.GetLatestVersion))
|
e.GET("/character", deprecatedMiddleware(index.GetCharacter))
|
||||||
e.GET("/version/history", deprecatedMiddleware(version.GetVersionHistory))
|
e.GET("/characters", deprecatedMiddleware(index.GetCharacterList))
|
||||||
|
|
||||||
character := NewCharacterHandler()
|
|
||||||
e.GET("/character", deprecatedMiddleware(character.GetCharacter))
|
|
||||||
e.GET("/characters", deprecatedMiddleware(character.GetCharacterList))
|
|
||||||
|
|
||||||
download := NewDownloadHandler()
|
download := NewDownloadHandler()
|
||||||
e.GET("/download", deprecatedMiddleware(download.checkLatest))
|
e.GET("/download", deprecatedMiddleware(download.checkLatest))
|
||||||
|
|||||||
@@ -73,11 +73,6 @@ func TestPartialMigrationThenSyncThenComplete(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, http.StatusOK, rec.Code)
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
// Wait for sync to complete
|
|
||||||
if !waitForSyncCompletion(t, e, 60) {
|
|
||||||
t.Error("Sync did not complete within timeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify data via statistics endpoint
|
// Verify data via statistics endpoint
|
||||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/statistics/summary", nil)
|
req = httptest.NewRequest(http.MethodGet, "/api/v1/statistics/summary", nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
@@ -90,9 +85,9 @@ func TestPartialMigrationThenSyncThenComplete(t *testing.T) {
|
|||||||
err := json.Unmarshal(rec.Body.Bytes(), &summary)
|
err := json.Unmarshal(rec.Body.Bytes(), &summary)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// After sync with /sync/new, only soundtracks matching filesystem remain
|
// We inserted 5 soundtracks, so total should be at least 5
|
||||||
// testMusic has 3 games
|
// (there might be existing data)
|
||||||
require.Equal(t, int64(3), summary.TotalGames)
|
require.GreaterOrEqual(t, summary.TotalGames, int64(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertTestData inserts 5 test soundtracks with songs into the database
|
// insertTestData inserts 5 test soundtracks with songs into the database
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ func (s *SyncHandler) SyncSoundtracksNewOnlyChanges(ctx *echo.Context) error {
|
|||||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||||
}
|
}
|
||||||
logging.GetLogger().Info("Starting sync with only changes")
|
logging.GetLogger().Info("Starting sync with only changes")
|
||||||
backend.Syncing = true
|
|
||||||
go backend.SyncSoundtracksNewOnlyChanges()
|
go backend.SyncSoundtracksNewOnlyChanges()
|
||||||
return ctx.JSON(http.StatusOK, "Start syncing soundtracks")
|
return ctx.JSON(http.StatusOK, "Start syncing soundtracks")
|
||||||
}
|
}
|
||||||
@@ -69,7 +68,6 @@ func (s *SyncHandler) SyncSoundtracksNewFull(ctx *echo.Context) error {
|
|||||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||||
}
|
}
|
||||||
logging.GetLogger().Info("Starting full sync")
|
logging.GetLogger().Info("Starting full sync")
|
||||||
backend.Syncing = true
|
|
||||||
go backend.SyncSoundtracksNewFull()
|
go backend.SyncSoundtracksNewFull()
|
||||||
return ctx.JSON(http.StatusOK, "Start syncing soundtracks full")
|
return ctx.JSON(http.StatusOK, "Start syncing soundtracks full")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ func StartTestServer(t *testing.T) *echo.Echo {
|
|||||||
s := &Server{
|
s := &Server{
|
||||||
db: db.TestDatabase,
|
db: db.TestDatabase,
|
||||||
tokenHandler: NewTokenHandler(db.TestDatabase.Pool),
|
tokenHandler: NewTokenHandler(db.TestDatabase.Pool),
|
||||||
statisticsHandler: NewStatisticsHandler(),
|
|
||||||
}
|
}
|
||||||
handler := s.RegisterRoutes()
|
handler := s.RegisterRoutes()
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v5"
|
|
||||||
"music-server/internal/backend"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VersionHandler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVersionHandler() *VersionHandler {
|
|
||||||
return &VersionHandler{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVersionHistory godoc
|
|
||||||
//
|
|
||||||
// @Summary Getting the version history of the backend
|
|
||||||
// @Description get version history
|
|
||||||
// @Tags version
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {array} backend.VersionData
|
|
||||||
// @Failure 404 {object} string
|
|
||||||
// @Router /version/history [get]
|
|
||||||
func (v *VersionHandler) GetVersionHistory(ctx *echo.Context) error {
|
|
||||||
versionHistory := backend.GetVersionHistory()
|
|
||||||
if len(versionHistory) == 0 {
|
|
||||||
return ctx.JSON(http.StatusNotFound, "version not found")
|
|
||||||
}
|
|
||||||
return ctx.JSON(http.StatusOK, versionHistory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLatestVersion godoc
|
|
||||||
//
|
|
||||||
// @Summary Getting the latest version of the backend
|
|
||||||
// @Description get latest version info
|
|
||||||
// @Tags version
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} backend.VersionData
|
|
||||||
// @Failure 404 {object} string
|
|
||||||
// @Router /version [get]
|
|
||||||
func (v *VersionHandler) GetLatestVersion(ctx *echo.Context) error {
|
|
||||||
latestVersion := backend.GetLatestVersion()
|
|
||||||
if latestVersion.Version == "" {
|
|
||||||
return ctx.JSON(http.StatusNotFound, "version not found")
|
|
||||||
}
|
|
||||||
return ctx.JSON(http.StatusOK, latestVersion)
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"music-server/internal/backend"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestGetLatestVersion verifies the version endpoint returns latest version
|
|
||||||
func TestGetLatestVersion(t *testing.T) {
|
|
||||||
e := StartTestServer(t)
|
|
||||||
|
|
||||||
resp := MakeTestRequest(t, e, "GET", "/version")
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
|
|
||||||
var versionData backend.VersionData
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &versionData)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, versionData.Version)
|
|
||||||
assert.NotEmpty(t, versionData.Changelog)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestGetVersionHistory verifies the version history endpoint returns version history
|
|
||||||
func TestGetVersionHistory(t *testing.T) {
|
|
||||||
e := StartTestServer(t)
|
|
||||||
|
|
||||||
resp := MakeTestRequest(t, e, "GET", "/version/history")
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
|
|
||||||
var versionHistory []backend.VersionData
|
|
||||||
err := json.Unmarshal(resp.Body.Bytes(), &versionHistory)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, versionHistory)
|
|
||||||
assert.NotEmpty(t, versionHistory[0].Version)
|
|
||||||
assert.NotEmpty(t, versionHistory[0].Changelog)
|
|
||||||
}
|
|
||||||
@@ -80,17 +80,9 @@ run:
|
|||||||
@templ generate
|
@templ generate
|
||||||
@go run cmd/main.go
|
@go run cmd/main.go
|
||||||
|
|
||||||
build-run: build
|
|
||||||
@go run cmd/main.go
|
|
||||||
|
|
||||||
test: build
|
test: build
|
||||||
@echo "Starting test database container..."
|
@echo "Testing..."
|
||||||
@podman-compose -f compose.test.yaml up -d
|
@go test ./... -v
|
||||||
@sleep 10
|
|
||||||
@echo "Running integration tests..."
|
|
||||||
@just test-integration
|
|
||||||
@echo "Stopping test database container..."
|
|
||||||
@just test-integration-down
|
|
||||||
|
|
||||||
# Clean the binary
|
# Clean the binary
|
||||||
clean:
|
clean:
|
||||||
@@ -110,9 +102,7 @@ podman-down:
|
|||||||
# Run integration tests with podman
|
# Run integration tests with podman
|
||||||
# Starts a test PostgreSQL container, runs tests, then cleans up
|
# Starts a test PostgreSQL container, runs tests, then cleans up
|
||||||
test-integration:
|
test-integration:
|
||||||
@echo "Cleaning old test database..."
|
@echo "Starting test database container..."
|
||||||
@podman-compose -f compose.test.yaml down -v
|
|
||||||
@echo "Starting fresh test database container..."
|
|
||||||
@podman-compose -f compose.test.yaml up -d
|
@podman-compose -f compose.test.yaml up -d
|
||||||
@sleep 10
|
@sleep 10
|
||||||
@echo "Running integration tests..."
|
@echo "Running integration tests..."
|
||||||
|
|||||||
Reference in New Issue
Block a user