diff --git a/grpc-backend/Makefile b/grpc-backend/Makefile index d2bd858..ab109ce 100644 --- a/grpc-backend/Makefile +++ b/grpc-backend/Makefile @@ -15,6 +15,8 @@ PROTOC_GEN_OPENAPIV2 ?= protoc-gen-openapiv2 all: generate generate: + cp ../osu_music.proto proto/osu_music.proto + $(PROTOC) \ -I $(PROTO_DIR) \ -I $(GOOGLEAPIS_DIR) \ @@ -27,4 +29,4 @@ generate: clean: rm -rf $(OUT_DIR) -.PHONY: all generate clean \ No newline at end of file +.PHONY: all generate clean diff --git a/grpc-backend/docs/docs.go b/grpc-backend/docs/docs.go deleted file mode 100644 index 5f06b61..0000000 --- a/grpc-backend/docs/docs.go +++ /dev/null @@ -1,683 +0,0 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT -package docs - -import "github.com/swaggo/swag" - -const docTemplate = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "contact": {}, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/audio/{filepath}": { - "get": { - "description": "Retrieves a song file from the server based on the provided encoded filepath", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "files" - ], - "summary": "Retrieves a song file by its encoded path", - "parameters": [ - { - "type": "string", - "description": "Base64 encoded file path", - "name": "filepath", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested song file", - "schema": { - "type": "file" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "File Not Found", - "schema": { - "type": "string" - } - } - } - } - }, - "/collection": { - "get": { - "description": "Retrieves a collection of songs using the provided index.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a collection of songs by index", - "parameters": [ - { - "type": "integer", - "description": "Index", - "name": "index", - "in": "query" - }, - { - "type": "string", - "description": "Index", - "name": "name", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "400": { - "description": "Invalid parameter", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - }, - "/image/{filepath}": { - "get": { - "description": "Retrieves an image file from the server based on the provided encoded filepath", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "files" - ], - "summary": "Retrieves an image file by its encoded path", - "parameters": [ - { - "type": "string", - "description": "Base64 encoded file path", - "name": "filepath", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested image file", - "schema": { - "type": "file" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "File Not Found", - "schema": { - "type": "string" - } - } - } - } - }, - "/login": { - "get": { - "description": "Redirects users to an external authentication page", - "tags": [ - "auth" - ], - "summary": "Redirect to login page", - "responses": { - "307": { - "description": "Temporary Redirect", - "schema": { - "type": "string" - } - } - } - } - }, - "/ping": { - "get": { - "description": "Returns a pong response if the server is running", - "tags": [ - "health" - ], - "summary": "Check server health", - "responses": { - "200": { - "description": "pong", - "schema": { - "type": "string" - } - } - } - } - }, - "/search/active": { - "get": { - "description": "Searches active records in the database based on the query parameter", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Searches active records based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit the number of results", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Active search result", - "schema": { - "$ref": "#/definitions/main.ActiveSearch" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/search/artist": { - "get": { - "description": "Searches for artists in the database based on the query parameter", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Searches for artists based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit the number of results", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of artists", - "schema": { - "$ref": "#/definitions/main.Artist" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/search/collections": { - "get": { - "description": "Searches collections in the database based on the query parameter", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Searches collections based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit the number of results", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of collections", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Collection" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/song/{hash}": { - "get": { - "description": "Retrieves a song using its unique hash identifier.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a song by its hash", - "parameters": [ - { - "type": "string", - "description": "Song hash", - "name": "hash", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/main.Song" - } - }, - "400": { - "description": "Invalid parameter", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Song not found", - "schema": { - "type": "string" - } - } - } - } - }, - "/songs/artist": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Returns all the Songs of a specific Artist", - "parameters": [ - { - "type": "string", - "description": "Artist Name", - "name": "artist", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/songs/favorites": { - "get": { - "description": "Retrieves favorite songs filtered by a query with pagination support.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a list of favorite songs based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "400": { - "description": "Invalid parameter", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - }, - "/songs/recents": { - "get": { - "description": "Retrieves recent songs with pagination support.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a list of recent songs", - "parameters": [ - { - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - } - }, - "definitions": { - "main.ActiveSearch": { - "description": "ActiveSearch holds search results for a given artist", - "type": "object", - "properties": { - "artist": { - "type": "string", - "example": "Ed Sheeran" - }, - "songs": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - } - }, - "main.Artist": { - "description": "Artist holds search results for a given artist", - "type": "object", - "properties": { - "artist": { - "type": "string", - "example": "Miku" - }, - "count": { - "type": "integer", - "example": 21 - } - } - }, - "main.Collection": { - "description": "Collection holds a list of songs", - "type": "object", - "properties": { - "items": { - "type": "integer", - "example": 15 - }, - "name": { - "type": "string", - "example": "Best of 2023" - }, - "songs": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - } - }, - "main.Song": { - "description": "Song represents a song with metadata", - "type": "object", - "properties": { - "artist": { - "type": "string", - "example": "Ed Sheeran" - }, - "audio": { - "type": "string", - "example": "audio.mp3" - }, - "beatmap_id": { - "type": "integer", - "example": 123456 - }, - "creator": { - "type": "string", - "example": "JohnDoe" - }, - "file": { - "type": "string", - "example": "beatmap.osu" - }, - "folder": { - "type": "string", - "example": "osu/Songs/123456" - }, - "image": { - "type": "string", - "example": "cover.jpg" - }, - "md5_hash": { - "type": "string", - "example": "abcd1234efgh5678" - }, - "title": { - "type": "string", - "example": "Shape of You" - }, - "total_time": { - "type": "integer", - "example": 240 - } - } - } - } -}` - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = &swag.Spec{ - Version: "", - Host: "", - BasePath: "", - Schemes: []string{}, - Title: "", - Description: "", - InfoInstanceName: "swagger", - SwaggerTemplate: docTemplate, -} - -func init() { - swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) -} diff --git a/grpc-backend/docs/swagger.json b/grpc-backend/docs/swagger.json deleted file mode 100644 index 7177a98..0000000 --- a/grpc-backend/docs/swagger.json +++ /dev/null @@ -1,656 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "contact": {} - }, - "paths": { - "/audio/{filepath}": { - "get": { - "description": "Retrieves a song file from the server based on the provided encoded filepath", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "files" - ], - "summary": "Retrieves a song file by its encoded path", - "parameters": [ - { - "type": "string", - "description": "Base64 encoded file path", - "name": "filepath", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested song file", - "schema": { - "type": "file" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "File Not Found", - "schema": { - "type": "string" - } - } - } - } - }, - "/collection": { - "get": { - "description": "Retrieves a collection of songs using the provided index.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a collection of songs by index", - "parameters": [ - { - "type": "integer", - "description": "Index", - "name": "index", - "in": "query" - }, - { - "type": "string", - "description": "Index", - "name": "name", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "400": { - "description": "Invalid parameter", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - }, - "/image/{filepath}": { - "get": { - "description": "Retrieves an image file from the server based on the provided encoded filepath", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "files" - ], - "summary": "Retrieves an image file by its encoded path", - "parameters": [ - { - "type": "string", - "description": "Base64 encoded file path", - "name": "filepath", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "The requested image file", - "schema": { - "type": "file" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "404": { - "description": "File Not Found", - "schema": { - "type": "string" - } - } - } - } - }, - "/login": { - "get": { - "description": "Redirects users to an external authentication page", - "tags": [ - "auth" - ], - "summary": "Redirect to login page", - "responses": { - "307": { - "description": "Temporary Redirect", - "schema": { - "type": "string" - } - } - } - } - }, - "/ping": { - "get": { - "description": "Returns a pong response if the server is running", - "tags": [ - "health" - ], - "summary": "Check server health", - "responses": { - "200": { - "description": "pong", - "schema": { - "type": "string" - } - } - } - } - }, - "/search/active": { - "get": { - "description": "Searches active records in the database based on the query parameter", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Searches active records based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit the number of results", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Active search result", - "schema": { - "$ref": "#/definitions/main.ActiveSearch" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/search/artist": { - "get": { - "description": "Searches for artists in the database based on the query parameter", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Searches for artists based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit the number of results", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of artists", - "schema": { - "$ref": "#/definitions/main.Artist" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/search/collections": { - "get": { - "description": "Searches collections in the database based on the query parameter", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "search" - ], - "summary": "Searches collections based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit the number of results", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset for pagination", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "List of collections", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Collection" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/song/{hash}": { - "get": { - "description": "Retrieves a song using its unique hash identifier.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a song by its hash", - "parameters": [ - { - "type": "string", - "description": "Song hash", - "name": "hash", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/main.Song" - } - }, - "400": { - "description": "Invalid parameter", - "schema": { - "type": "string" - } - }, - "404": { - "description": "Song not found", - "schema": { - "type": "string" - } - } - } - } - }, - "/songs/artist": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Returns all the Songs of a specific Artist", - "parameters": [ - { - "type": "string", - "description": "Artist Name", - "name": "artist", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "type": "string" - } - } - } - } - }, - "/songs/favorites": { - "get": { - "description": "Retrieves favorite songs filtered by a query with pagination support.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a list of favorite songs based on a query", - "parameters": [ - { - "type": "string", - "description": "Search query", - "name": "query", - "in": "query", - "required": true - }, - { - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "400": { - "description": "Invalid parameter", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - }, - "/songs/recents": { - "get": { - "description": "Retrieves recent songs with pagination support.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "songs" - ], - "summary": "Get a list of recent songs", - "parameters": [ - { - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - } - }, - "definitions": { - "main.ActiveSearch": { - "description": "ActiveSearch holds search results for a given artist", - "type": "object", - "properties": { - "artist": { - "type": "string", - "example": "Ed Sheeran" - }, - "songs": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - } - }, - "main.Artist": { - "description": "Artist holds search results for a given artist", - "type": "object", - "properties": { - "artist": { - "type": "string", - "example": "Miku" - }, - "count": { - "type": "integer", - "example": 21 - } - } - }, - "main.Collection": { - "description": "Collection holds a list of songs", - "type": "object", - "properties": { - "items": { - "type": "integer", - "example": 15 - }, - "name": { - "type": "string", - "example": "Best of 2023" - }, - "songs": { - "type": "array", - "items": { - "$ref": "#/definitions/main.Song" - } - } - } - }, - "main.Song": { - "description": "Song represents a song with metadata", - "type": "object", - "properties": { - "artist": { - "type": "string", - "example": "Ed Sheeran" - }, - "audio": { - "type": "string", - "example": "audio.mp3" - }, - "beatmap_id": { - "type": "integer", - "example": 123456 - }, - "creator": { - "type": "string", - "example": "JohnDoe" - }, - "file": { - "type": "string", - "example": "beatmap.osu" - }, - "folder": { - "type": "string", - "example": "osu/Songs/123456" - }, - "image": { - "type": "string", - "example": "cover.jpg" - }, - "md5_hash": { - "type": "string", - "example": "abcd1234efgh5678" - }, - "title": { - "type": "string", - "example": "Shape of You" - }, - "total_time": { - "type": "integer", - "example": 240 - } - } - } - } -} \ No newline at end of file diff --git a/grpc-backend/docs/swagger.yaml b/grpc-backend/docs/swagger.yaml deleted file mode 100644 index d2f91db..0000000 --- a/grpc-backend/docs/swagger.yaml +++ /dev/null @@ -1,441 +0,0 @@ -definitions: - main.ActiveSearch: - description: ActiveSearch holds search results for a given artist - properties: - artist: - example: Ed Sheeran - type: string - songs: - items: - $ref: '#/definitions/main.Song' - type: array - type: object - main.Artist: - description: Artist holds search results for a given artist - properties: - artist: - example: Miku - type: string - count: - example: 21 - type: integer - type: object - main.Collection: - description: Collection holds a list of songs - properties: - items: - example: 15 - type: integer - name: - example: Best of 2023 - type: string - songs: - items: - $ref: '#/definitions/main.Song' - type: array - type: object - main.Song: - description: Song represents a song with metadata - properties: - artist: - example: Ed Sheeran - type: string - audio: - example: audio.mp3 - type: string - beatmap_id: - example: 123456 - type: integer - creator: - example: JohnDoe - type: string - file: - example: beatmap.osu - type: string - folder: - example: osu/Songs/123456 - type: string - image: - example: cover.jpg - type: string - md5_hash: - example: abcd1234efgh5678 - type: string - title: - example: Shape of You - type: string - total_time: - example: 240 - type: integer - type: object -info: - contact: {} -paths: - /audio/{filepath}: - get: - consumes: - - application/json - description: Retrieves a song file from the server based on the provided encoded - filepath - parameters: - - description: Base64 encoded file path - in: path - name: filepath - required: true - type: string - produces: - - application/json - responses: - "200": - description: The requested song file - schema: - type: file - "400": - description: Bad Request - schema: - type: string - "404": - description: File Not Found - schema: - type: string - summary: Retrieves a song file by its encoded path - tags: - - files - /collection: - get: - consumes: - - application/json - description: Retrieves a collection of songs using the provided index. - parameters: - - description: Index - in: query - name: index - type: integer - - description: Index - in: query - name: name - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/main.Song' - type: array - "400": - description: Invalid parameter - schema: - type: string - "500": - description: Internal server error - schema: - type: string - summary: Get a collection of songs by index - tags: - - songs - /image/{filepath}: - get: - consumes: - - application/json - description: Retrieves an image file from the server based on the provided encoded - filepath - parameters: - - description: Base64 encoded file path - in: path - name: filepath - required: true - type: string - produces: - - application/json - responses: - "200": - description: The requested image file - schema: - type: file - "400": - description: Bad Request - schema: - type: string - "404": - description: File Not Found - schema: - type: string - summary: Retrieves an image file by its encoded path - tags: - - files - /login: - get: - description: Redirects users to an external authentication page - responses: - "307": - description: Temporary Redirect - schema: - type: string - summary: Redirect to login page - tags: - - auth - /ping: - get: - description: Returns a pong response if the server is running - responses: - "200": - description: pong - schema: - type: string - summary: Check server health - tags: - - health - /search/active: - get: - consumes: - - application/json - description: Searches active records in the database based on the query parameter - parameters: - - description: Search query - in: query - name: query - required: true - type: string - - default: 10 - description: Limit the number of results - in: query - name: limit - type: integer - - default: 0 - description: Offset for pagination - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Active search result - schema: - $ref: '#/definitions/main.ActiveSearch' - "400": - description: Bad Request - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Searches active records based on a query - tags: - - search - /search/artist: - get: - consumes: - - application/json - description: Searches for artists in the database based on the query parameter - parameters: - - description: Search query - in: query - name: query - required: true - type: string - - default: 10 - description: Limit the number of results - in: query - name: limit - type: integer - - default: 0 - description: Offset for pagination - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: List of artists - schema: - $ref: '#/definitions/main.Artist' - "400": - description: Bad Request - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Searches for artists based on a query - tags: - - search - /search/collections: - get: - consumes: - - application/json - description: Searches collections in the database based on the query parameter - parameters: - - description: Search query - in: query - name: query - required: true - type: string - - default: 10 - description: Limit the number of results - in: query - name: limit - type: integer - - default: 0 - description: Offset for pagination - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: List of collections - schema: - items: - $ref: '#/definitions/main.Collection' - type: array - "400": - description: Bad Request - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Searches collections based on a query - tags: - - search - /song/{hash}: - get: - consumes: - - application/json - description: Retrieves a song using its unique hash identifier. - parameters: - - description: Song hash - in: path - name: hash - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/main.Song' - "400": - description: Invalid parameter - schema: - type: string - "404": - description: Song not found - schema: - type: string - summary: Get a song by its hash - tags: - - songs - /songs/artist: - get: - consumes: - - application/json - parameters: - - description: Artist Name - in: query - name: artist - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/main.Song' - type: array - "400": - description: Bad Request - schema: - type: string - "500": - description: Internal Server Error - schema: - type: string - summary: Returns all the Songs of a specific Artist - tags: - - songs - /songs/favorites: - get: - consumes: - - application/json - description: Retrieves favorite songs filtered by a query with pagination support. - parameters: - - description: Search query - in: query - name: query - required: true - type: string - - default: 10 - description: Limit - in: query - name: limit - type: integer - - default: 0 - description: Offset - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/main.Song' - type: array - "400": - description: Invalid parameter - schema: - type: string - "500": - description: Internal server error - schema: - type: string - summary: Get a list of favorite songs based on a query - tags: - - songs - /songs/recents: - get: - consumes: - - application/json - description: Retrieves recent songs with pagination support. - parameters: - - default: 10 - description: Limit - in: query - name: limit - type: integer - - default: 0 - description: Offset - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/main.Song' - type: array - "500": - description: Internal server error - schema: - type: string - summary: Get a list of recent songs - tags: - - songs -swagger: "2.0" diff --git a/grpc-backend/gen/swagger/osu_music.swagger.json b/grpc-backend/gen/swagger/osu_music.swagger.json new file mode 100644 index 0000000..3b92af1 --- /dev/null +++ b/grpc-backend/gen/swagger/osu_music.swagger.json @@ -0,0 +1,580 @@ +{ + "swagger": "2.0", + "info": { + "title": "osu_music.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "MusicBackend" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/api/v1/artist/{artist}": { + "get": { + "operationId": "MusicBackend_Artist", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ArtistResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "artist", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/collections": { + "get": { + "operationId": "MusicBackend_Collections", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CollectionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "name", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "index", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/favorites": { + "get": { + "operationId": "MusicBackend_Favorite", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1FavoriteResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/recent": { + "get": { + "operationId": "MusicBackend_Recent", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RecentResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/search": { + "get": { + "operationId": "MusicBackend_Search", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SearchSharedResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/search/artists": { + "get": { + "operationId": "MusicBackend_SearchArtists", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SearchArtistResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/search/collections": { + "get": { + "operationId": "MusicBackend_SearchCollections", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SearchCollectionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "query", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "offset", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/api/v1/song/{hash}": { + "get": { + "operationId": "MusicBackend_Song", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SongResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "hash", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "MusicBackend" + ] + } + }, + "/ping": { + "get": { + "operationId": "MusicBackend_Ping", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1PingResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "ping", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "MusicBackend" + ] + } + } + }, + "definitions": { + "apiv1Artist": { + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "items": { + "type": "integer", + "format": "int32" + } + } + }, + "apiv1Song": { + "type": "object", + "properties": { + "beatmapId": { + "type": "integer", + "format": "int32" + }, + "md5Hash": { + "type": "string" + }, + "title": { + "type": "string" + }, + "artist": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "folder": { + "type": "string" + }, + "file": { + "type": "string" + }, + "audio": { + "type": "string" + }, + "totalTime": { + "type": "string", + "format": "int64" + }, + "image": { + "type": "string" + } + }, + "title": "===== models =====" + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1ArtistResponse": { + "type": "object", + "properties": { + "songs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiv1Song" + } + } + } + }, + "v1CollectionPreview": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "image": { + "type": "string" + }, + "items": { + "type": "integer", + "format": "int32" + } + } + }, + "v1CollectionResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "items": { + "type": "integer", + "format": "int32" + }, + "songs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiv1Song" + } + } + } + }, + "v1FavoriteResponse": { + "type": "object", + "properties": { + "songs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiv1Song" + } + } + } + }, + "v1PingResponse": { + "type": "object", + "properties": { + "pong": { + "type": "string" + } + } + }, + "v1RecentResponse": { + "type": "object", + "properties": { + "songs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiv1Song" + } + } + } + }, + "v1SearchArtistResponse": { + "type": "object", + "properties": { + "Artists": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiv1Artist" + } + } + } + }, + "v1SearchCollectionResponse": { + "type": "object", + "properties": { + "collections": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1CollectionPreview" + } + } + } + }, + "v1SearchSharedResponse": { + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "songs": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiv1Song" + } + } + } + }, + "v1SongResponse": { + "type": "object", + "properties": { + "song": { + "$ref": "#/definitions/apiv1Song" + } + } + } + } +} diff --git a/grpc-backend/go.mod b/grpc-backend/go.mod index a86694c..ccacb9f 100644 --- a/grpc-backend/go.mod +++ b/grpc-backend/go.mod @@ -8,7 +8,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/joho/godotenv v1.5.1 github.com/juli0n21/go-osu-parser v0.0.8 - github.com/swaggo/http-swagger v1.3.4 google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 google.golang.org/grpc v1.72.1 google.golang.org/protobuf v1.36.6 @@ -16,28 +15,16 @@ require ( ) require ( - github.com/KyleBanks/depth v1.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/spec v0.20.6 // indirect - github.com/go-openapi/swag v0.19.15 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect - github.com/swaggo/swag v1.8.1 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/net v0.37.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect modernc.org/libc v1.62.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.9.1 // indirect diff --git a/grpc-backend/go.sum b/grpc-backend/go.sum index b859bf1..78b9bdb 100644 --- a/grpc-backend/go.sum +++ b/grpc-backend/go.sum @@ -1,27 +1,9 @@ -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= -github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= -github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -34,45 +16,14 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5uk github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/juli0n21/go-osu-parser v0.0.8 h1:aQtuhAniGvpUw446arhq/3aUOK9YvZEkL7aYUGlViAo= github.com/juli0n21/go-osu-parser v0.0.8/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= -github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= -github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= -github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= -github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= -github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -89,21 +40,15 @@ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= @@ -114,18 +59,6 @@ google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= diff --git a/grpc-backend/handlers.go b/grpc-backend/handlers.go index da1bd35..d02e0f8 100644 --- a/grpc-backend/handlers.go +++ b/grpc-backend/handlers.go @@ -1,21 +1,20 @@ package main import ( + "context" "database/sql" "encoding/base64" - "encoding/json" "errors" "fmt" "log" "net/http" "os" - "strconv" - _ "backend/docs" v1 "backend/gen" "github.com/joho/godotenv" - httpSwagger "github.com/swaggo/http-swagger" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/juli0n21/go-osu-parser/parser" ) @@ -34,36 +33,15 @@ type Server struct { v1.UnimplementedMusicBackendServer } -func (s *Server) registerRoutes() http.Handler { +func (s *Server) registerRoutes() *http.ServeMux { mux := http.NewServeMux() - mux.HandleFunc("/api/v1/ping", s.ping) - mux.HandleFunc("/api/v1/login", s.login) - - mux.HandleFunc("/api/v1/song/{hash}/", s.song) - mux.HandleFunc("/api/v1/songs/recent", s.recents) - mux.HandleFunc("/api/v1/songs/favorites", s.favorites) - mux.HandleFunc("/api/v1/songs/artist", s.aristsSongs) - - mux.HandleFunc("/api/v1/collection", s.collection) - mux.HandleFunc("/api/v1/search/collections", s.collectionSearch) - - mux.HandleFunc("/api/v1/search/active", s.activeSearch) - mux.HandleFunc("/api/v1/search/artist", s.artistSearch) - mux.HandleFunc("/api/v1/audio/{filepath}", s.songFile) mux.HandleFunc("/api/v1/image/{filepath}", s.imageFile) mux.HandleFunc("/api/v1/callback", s.callback) - mux.Handle("/swagger/", httpSwagger.WrapHandler) - return corsMiddleware(logRequests(mux)) -} - -func run(s *Server) { - mux := s.registerRoutes() - fmt.Println("starting server on http://localhost" + s.Port) - log.Fatal(http.ListenAndServe(s.Port, mux)) + return mux } func logRequests(next http.Handler) http.Handler { @@ -88,273 +66,161 @@ func corsMiddleware(next http.Handler) http.Handler { }) } -// ping godoc -// -// @Summary Check server health -// @Description Returns a pong response if the server is running -// @Tags health -// @Success 200 {string} string "pong" -// @Router /ping [get] -func (s *Server) ping(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "pong") +func (s *Server) Ping(ctx context.Context, req *v1.PingRequest) (*v1.PingResponse, error) { + return &v1.PingResponse{Pong: "pong"}, nil } -// login godoc -// -// @Summary Redirect to login page -// @Description Redirects users to an external authentication page -// @Tags auth -// @Success 307 {string} string "Temporary Redirect" -// @Router /login [get] func (s *Server) login(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect) } -// song godoc -// -// @Summary Get a song by its hash -// @Description Retrieves a song using its unique hash identifier. -// @Tags songs -// @Accept json -// @Produce json -// @Param hash path string true "Song hash" -// @Success 200 {object} Song -// @Failure 400 {string} string "Invalid parameter" -// @Failure 404 {string} string "Song not found" -// @Router /song/{hash} [get] -func (s *Server) song(w http.ResponseWriter, r *http.Request) { +func (s *Server) Song(ctx context.Context, req *v1.SongRequest) (*v1.SongResponse, error) { - hash := r.PathValue("hash") + hash := req.Hash if hash == "" { - http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest) - return + return nil, status.Errorf(codes.InvalidArgument, "hash is required and cant be empty") } song, err := getSong(s.Db, hash) if err != nil { fmt.Println(err) - http.Error(w, "beatmap not found by hash", http.StatusNotFound) - return + return nil, status.Errorf(codes.NotFound, "beatmap not found by hash") } - writeJSON(w, song, http.StatusOK) + return &v1.SongResponse{ + Song: song.toProto(), + }, nil } -// recents godoc -// -// @Summary Get a list of recent songs -// @Description Retrieves recent songs with pagination support. -// @Tags songs -// @Accept json -// @Produce json -// @Param limit query int false "Limit" default(10) -// @Param offset query int false "Offset" default(0) -// @Success 200 {array} Song -// @Failure 500 {string} string "Internal server error" -// @Router /songs/recents [get] -func (s *Server) recents(w http.ResponseWriter, r *http.Request) { +func (s *Server) Recent(ctx context.Context, req *v1.RecentRequest) (*v1.RecentResponse, error) { + + limit := defaultLimit(int(req.Limit)) + offset := int(req.Offset) - limit, offset := pagination(r) recent, err := getRecent(s.Db, limit, offset) if err != nil { fmt.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, status.Errorf(codes.Internal, "failed to get recents") } - writeJSON(w, recent, http.StatusOK) + + return &v1.RecentResponse{ + Songs: toProtoSongs(recent), + }, nil } -// favorites godoc -// -// @Summary Get a list of favorite songs based on a query -// @Description Retrieves favorite songs filtered by a query with pagination support. -// @Tags songs -// @Accept json -// @Produce json -// @Param query query string true "Search query" -// @Param limit query int false "Limit" default(10) -// @Param offset query int false "Offset" default(0) -// @Success 200 {array} Song -// @Failure 400 {string} string "Invalid parameter" -// @Failure 500 {string} string "Internal server error" -// @Router /songs/favorites [get] -func (s *Server) favorites(w http.ResponseWriter, r *http.Request) { - query := r.URL.Query().Get("query") - if query == "" { - http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest) - return - } +func (s *Server) Favorite(ctx context.Context, req *v1.FavoriteRequest) (*v1.FavoriteResponse, error) { - limit, offset := pagination(r) - - favorites, err := getFavorites(s.Db, query, limit, offset) + + limit := defaultLimit(int(req.Limit)) + offset := int(req.Offset) + favorites, err := getFavorites(s.Db, req.Query, limit, offset) if err != nil { fmt.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, status.Errorf(codes.Internal, "failed to get favorites") } - writeJSON(w, favorites, http.StatusOK) + + return &v1.FavoriteResponse{ + Songs: toProtoSongs(favorites), + }, nil } -// collection godoc -// -// @Summary Get a collection of songs by index -// @Description Retrieves a collection of songs using the provided index. -// @Tags songs -// @Accept json -// @Produce json -// @Param index query int false "Index" -// @Param name query string false "Index" -// @Success 200 {array} Song -// @Failure 400 {string} string "Invalid parameter" -// @Failure 500 {string} string "Internal server error" -// @Router /collection [get] -func (s *Server) collection(w http.ResponseWriter, r *http.Request) { - limit, offset := pagination(r) - name := r.URL.Query().Get("name") +func (s *Server) Collections(ctx context.Context, req *v1.CollectionRequest) (*v1.CollectionResponse, error) { + + limit := defaultLimit(int(req.Limit)) + offset := int(req.Offset) + + + name := req.Name if name != "" { c, err := getCollectionByName(s.Db, limit, offset, name) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + fmt.Println(err) + return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with name: %s", name)) } - writeJSON(w, c, http.StatusOK) - return + return &v1.CollectionResponse{ + Songs: toProtoSongs(c.Songs), + Items: int32(c.Items), + Name: c.Name, + }, nil } - index, err := strconv.Atoi(r.URL.Query().Get("index")) + c, err := getCollection(s.Db, limit, offset, int(req.Index)) if err != nil { - http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest) - return - } else { - c, err := getCollection(s.Db, limit, offset, index) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - writeJSON(w, c, http.StatusOK) - return + fmt.Println(err) + return nil, status.Errorf(codes.Internal, fmt.Sprintf("failed to fetch collection with index: %d", req.Index)) } + return &v1.CollectionResponse{ + Songs: toProtoSongs(c.Songs), + Items: int32(c.Items), + Name: c.Name, + }, nil } -// @Summary Searches collections based on a query -// @Description Searches collections in the database based on the query parameter -// @Tags search -// @Accept json -// @Produce json -// @Param query query string true "Search query" -// @Param limit query int false "Limit the number of results" default(10) -// @Param offset query int false "Offset for pagination" default(0) -// @Success 200 {array} Collection "List of collections" -// @Failure 400 {object} string "Bad Request" -// @Failure 500 {object} string "Internal Server Error" -// @Router /search/collections [get] -func (s *Server) collectionSearch(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query().Get("query") +func (s *Server) Search(ctx context.Context, req *v1.SearchSharedRequest) (*v1.SearchSharedResponse, error) { + q := req.Query + if q == "" { + return nil, status.Error(codes.InvalidArgument, "query cant be empty") + } - limit, offset := pagination(r) + + limit := defaultLimit(int(req.Limit)) + offset := int(req.Offset) + + + search, err := getSearch(s.Db, q, limit, offset) + if err != nil { + fmt.Println(err) + return nil, status.Error(codes.Internal, "failed to fetch search") + } + + return &v1.SearchSharedResponse{ + Artist: search.Artist, + Songs: toProtoSongs(search.Songs), + }, nil +} + +func (s *Server) SearchCollections(ctx context.Context, req *v1.SearchCollectionRequest) (*v1.SearchCollectionResponse, error) { + q := req.Query + if q == "" { + return nil, status.Errorf(codes.InvalidArgument, "query is required") + } + + + limit := defaultLimit(int(req.Limit)) + offset := int(req.Offset) + preview, err := getCollections(s.Db, q, limit, offset) if err != nil { fmt.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - writeJSON(w, preview, http.StatusOK) -} - -// @Summary Returns all the Songs of a specific Artist -// @Tags songs -// @Accept json -// @Produce json -// @Param artist query string true "Artist Name" -// @Success 200 {array} Song -// @Failure 400 {object} string "Bad Request" -// @Failure 500 {object} string "Internal Server Error" -// @Router /songs/artist [get] -func (s *Server) aristsSongs(w http.ResponseWriter, r *http.Request) { - artist := r.URL.Query().Get("artist") - if artist == "" { - http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest) - return + return nil, status.Errorf(codes.Internal, "failed to search for collections") } - writeJSON(w, []Song{}, http.StatusOK) + return &v1.SearchCollectionResponse{ + Collections: toProtoCollectionPreview(preview), + }, nil } -// @Summary Searches active records based on a query -// @Description Searches active records in the database based on the query parameter -// @Tags search -// @Accept json -// @Produce json -// @Param query query string true "Search query" -// @Param limit query int false "Limit the number of results" default(10) -// @Param offset query int false "Offset for pagination" default(0) -// @Success 200 {object} ActiveSearch "Active search result" -// @Failure 400 {object} string "Bad Request" -// @Failure 500 {object} string "Internal Server Error" -// @Router /search/active [get] -func (s *Server) activeSearch(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query().Get("query") +func (s *Server) SearchArtists(ctx context.Context, req *v1.SearchArtistRequest) (*v1.SearchArtistResponse, error) { + q := req.Query if q == "" { - http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest) - return + return nil, status.Error(codes.InvalidArgument, "query is required") } - //TODO - limit, offset := pagination(r) - - recent, err := getSearch(s.Db, q, limit, offset) - if err != nil { - fmt.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - writeJSON(w, recent, http.StatusOK) -} - -// @Summary Searches for artists based on a query -// @Description Searches for artists in the database based on the query parameter -// @Tags search -// @Accept json -// @Produce json -// @Param query query string true "Search query" -// @Param limit query int false "Limit the number of results" default(10) -// @Param offset query int false "Offset for pagination" default(0) -// @Success 200 {object} Artist "List of artists" -// @Failure 400 {object} string "Bad Request" -// @Failure 500 {object} string "Internal Server Error" -// @Router /search/artist [get] -func (s *Server) artistSearch(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query().Get("query") - if q == "" { - http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest) - return - } - - //TODO - limit, offset := pagination(r) + limit := defaultLimit(int(req.Limit)) + offset := int(req.Offset) a, err := getArtists(s.Db, q, limit, offset) if err != nil { fmt.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return + return nil, status.Error(codes.Internal, "failed to search artists") } - writeJSON(w, a, http.StatusOK) + return &v1.SearchArtistResponse{ + Artists: toProtoArtist(a), + }, nil } -// @Summary Retrieves a song file by its encoded path -// @Description Retrieves a song file from the server based on the provided encoded filepath -// @Tags files -// @Accept json -// @Produce json -// @Param filepath path string true "Base64 encoded file path" -// @Success 200 {file} File "The requested song file" -// @Failure 400 {object} string "Bad Request" -// @Failure 404 {object} string "File Not Found" -// @Router /audio/{filepath} [get] func (s *Server) songFile(w http.ResponseWriter, r *http.Request) { f := r.PathValue("filepath") if f == "" { @@ -387,16 +253,6 @@ func (s *Server) songFile(w http.ResponseWriter, r *http.Request) { http.ServeContent(w, r, stat.Name(), stat.ModTime(), file) } -// @Summary Retrieves an image file by its encoded path -// @Description Retrieves an image file from the server based on the provided encoded filepath -// @Tags files -// @Accept json -// @Produce json -// @Param filepath path string true "Base64 encoded file path" -// @Success 200 {file} File "The requested image file" -// @Failure 400 {object} string "Bad Request" -// @Failure 404 {object} string "File Not Found" -// @Router /image/{filepath} [get] func (s *Server) imageFile(w http.ResponseWriter, r *http.Request) { f := r.PathValue("filepath") if f == "" { @@ -423,24 +279,9 @@ func (s *Server) callback(w http.ResponseWriter, r *http.Request) { } } -func writeJSON(w http.ResponseWriter, v any, status int) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - if err := json.NewEncoder(w).Encode(v); err != nil { - fmt.Println(err) - http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError) +func defaultLimit(limit int) int { + if limit <= 0 || limit > 100 { + return 100 } -} - -func pagination(r *http.Request) (int, int) { - limit, err := strconv.Atoi(r.URL.Query().Get("limit")) - if err != nil || limit <= 0 || limit > 100 { - limit = 100 - } - offset, err := strconv.Atoi(r.URL.Query().Get("offset")) - if err != nil || offset < 0 { - offset = 0 - } - - return limit, offset + return limit } diff --git a/grpc-backend/main.go b/grpc-backend/main.go index c9f4b00..47f7e2b 100644 --- a/grpc-backend/main.go +++ b/grpc-backend/main.go @@ -78,7 +78,6 @@ func main() { Env: envMap, } - // Run gRPC + grpc-gateway servers if err := runGrpcAndGateway(s, port); err != nil { log.Fatalf("Failed to run servers: %v", err) } @@ -169,15 +168,15 @@ func sendUrl(endpoint, cookie string) error { } func runGrpcAndGateway(s *Server, port string) error { - grpcPort := ":9090" // gRPC server port - httpPort := port // REST gateway port (e.g. ":8080") + grpcPort := ":9090" + httpPort := port grpcLis, err := net.Listen("tcp", grpcPort) if err != nil { return fmt.Errorf("failed to listen on %s: %w", grpcPort, err) } grpcServer := grpc.NewServer() - v1.RegisterMusicBackendServer(grpcServer, s) // Register your service implementation + v1.RegisterMusicBackendServer(grpcServer, s) ctx := context.Background() ctx, cancel := context.WithCancel(ctx) @@ -191,20 +190,25 @@ func runGrpcAndGateway(s *Server, port string) error { return fmt.Errorf("failed to register grpc-gateway: %w", err) } - mux := http.NewServeMux() + mux := &http.ServeMux{} - mux.Handle("/api/v1/", gwMux) - - mux.HandleFunc("/files", s.songFile) + mux.HandleFunc("/callback/", s.callback) + mux.HandleFunc("api/v1/audio/{filepath}", s.songFile) + mux.HandleFunc("api/v1/image/{filepath}", s.imageFile) fileServer := http.FileServer(http.Dir("gen/swagger")) mux.Handle("/swagger/", http.StripPrefix("/swagger/", fileServer)) httpServer := &http.Server{ Addr: httpPort, - Handler: mux, + Handler: corsMiddleware(logRequests(mux)), } + mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("HTTP %s %s", r.Method, r.URL.Path) + gwMux.ServeHTTP(w, r) + })) + stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) diff --git a/grpc-backend/models.go b/grpc-backend/models.go index 41fb4a9..00ecc58 100644 --- a/grpc-backend/models.go +++ b/grpc-backend/models.go @@ -1,5 +1,7 @@ package main +import v1 "backend/gen" + // Song represents a song entity // @Description Song represents a song with metadata type Song struct { @@ -15,6 +17,30 @@ type Song struct { Image string `json:"image" example:"cover.jpg"` } +func (song Song) toProto() *v1.Song { + return &v1.Song{ + BeatmapId: int32(song.BeatmapID), + Md5Hash: song.MD5Hash, + Title: song.Title, + Artist: song.Artist, + Creator: song.Creator, + Folder: song.Folder, + File: song.File, + Audio: song.Audio, + TotalTime: song.TotalTime, + Image: song.Image, + } +} + +func toProtoSongs(local []Song) []*v1.Song { + songs := make([]*v1.Song, len(local)) + for i, s := range local { + songs[i] = s.toProto() + } + + return songs +} + // CollectionPreview represents a preview of a song collection // @Description CollectionPreview contains summary data of a song collection type CollectionPreview struct { @@ -23,6 +49,23 @@ type CollectionPreview struct { Items int `json:"items" example:"10"` } +func (c CollectionPreview) toProto() *v1.CollectionPreview { + return &v1.CollectionPreview{ + Name: c.Name, + Image: c.Image, + Items: int32(c.Items), + } +} + +func toProtoCollectionPreview(local []CollectionPreview) []*v1.CollectionPreview { + collection := make([]*v1.CollectionPreview, len(local)) + for i, c := range local { + collection[i] = c.toProto() + } + + return collection +} + // Collection represents a full song collection // @Description Collection holds a list of songs type Collection struct { @@ -44,3 +87,19 @@ type Artist struct { Artist string `json:"artist" example:"Miku"` Count int `json:"count" example:"21"` } + +func (a Artist) toProto() *v1.Artist { + return &v1.Artist{ + Artist: a.Artist, + Items: int32(a.Count), + } +} + +func toProtoArtist(local []Artist) []*v1.Artist { + artists := make([]*v1.Artist, len(local)) + for i, a := range local { + artists[i] = a.toProto() + } + + return artists +} diff --git a/grpc-backend/proto/file_specs.json b/grpc-backend/proto/file_specs.json new file mode 100644 index 0000000..5a13112 --- /dev/null +++ b/grpc-backend/proto/file_specs.json @@ -0,0 +1,46 @@ +{ + "paths": { + "/api/v1/audio/{filepath}": { + "get": { + "summary": "Serve audio files (base64-encoded path)", + "parameters": [ + { + "name": "filepath", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Base64-encoded file path" + } + ], + "responses": { + "200": { + "description": "Audio file content" + } + } + } + }, + "/api/v1/image/{filepath}": { + "get": { + "summary": "Serve image files (base64-encoded path)", + "parameters": [ + { + "name": "filepath", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Base64-encoded file path" + } + ], + "responses": { + "200": { + "description": "Image file content" + } + } + } + } + } +} diff --git a/grpc-backend/proto/osu_music.proto b/grpc-backend/proto/osu_music.proto index 17b822b..02529db 100644 --- a/grpc-backend/proto/osu_music.proto +++ b/grpc-backend/proto/osu_music.proto @@ -9,49 +9,49 @@ import "google/api/annotations.proto"; service MusicBackend { rpc Collections(CollectionRequest) returns (CollectionResponse) { option (google.api.http) = { - get: "/v1/collections" + get: "/api/v1/collections" }; } rpc Song(SongRequest) returns (SongResponse){ option (google.api.http) = { - get: "/v1/song/{hash}" + get: "/api/v1/song/{hash}" }; } rpc Artist(ArtistRequest) returns (ArtistResponse){ option (google.api.http) = { - get: "/v1/artist/{artist}" + get: "/api/v1/artist/{artist}" }; } rpc Favorite(FavoriteRequest) returns (FavoriteResponse){ option (google.api.http) = { - get: "/v1/favorites" + get: "/api/v1/favorites" }; } rpc Recent(RecentRequest) returns (RecentResponse){ option (google.api.http) = { - get: "/v1/recent" + get: "/api/v1/recent" }; } rpc Search(SearchSharedRequest) returns (SearchSharedResponse) { option (google.api.http) = { - get: "/v1/search" + get: "/api/v1/search" }; } rpc SearchArtists(SearchArtistRequest) returns (SearchArtistResponse) { option (google.api.http) = { - get: "/v1/search/artists" + get: "/api/v1/search/artists" }; } rpc SearchCollections(SearchCollectionRequest) returns (SearchCollectionResponse) { option (google.api.http) = { - get: "/v1/search/collections" + get: "/api/v1/search/collections" }; } rpc Ping(PingRequest) returns (PingResponse) { option (google.api.http) = { get: "/ping" - }; + }; } } @@ -59,6 +59,8 @@ service MusicBackend { message CollectionRequest { string name = 1; int32 index = 2; + int32 limit = 3; + int32 offset = 4; } message CollectionResponse { @@ -73,7 +75,7 @@ message SongRequest { } message SongResponse { - repeated Song songs = 1; + Song song = 1; } message ArtistRequest { @@ -122,6 +124,10 @@ message SearchArtistRequest { } message SearchArtistResponse { + repeated Artist Artists = 1; +} + +message Artist { string artist = 1; int32 items = 2; } @@ -133,11 +139,15 @@ message SearchCollectionRequest { } message SearchCollectionResponse { + repeated CollectionPreview collections = 1; +} + +message CollectionPreview { string name = 1; string image = 2; int32 items = 3; -} +} //===== status ===== message PingRequest { string ping = 1; @@ -159,4 +169,4 @@ message Song { string audio = 8; int64 total_time = 9; string image = 10; -} +} \ No newline at end of file diff --git a/osu_music.proto b/osu_music.proto index c0f1beb..02529db 100644 --- a/osu_music.proto +++ b/osu_music.proto @@ -9,49 +9,49 @@ import "google/api/annotations.proto"; service MusicBackend { rpc Collections(CollectionRequest) returns (CollectionResponse) { option (google.api.http) = { - get: "/v1/collections" + get: "/api/v1/collections" }; } rpc Song(SongRequest) returns (SongResponse){ option (google.api.http) = { - get: "/v1/song/{hash}" + get: "/api/v1/song/{hash}" }; } rpc Artist(ArtistRequest) returns (ArtistResponse){ option (google.api.http) = { - get: "/v1/artist{artist}" + get: "/api/v1/artist/{artist}" }; } rpc Favorite(FavoriteRequest) returns (FavoriteResponse){ option (google.api.http) = { - get: "/v1/favorites" + get: "/api/v1/favorites" }; } rpc Recent(RecentRequest) returns (RecentResponse){ option (google.api.http) = { - get: "/v1/recent" + get: "/api/v1/recent" }; } rpc Search(SearchSharedRequest) returns (SearchSharedResponse) { option (google.api.http) = { - get: "/v1/search" + get: "/api/v1/search" }; } rpc SearchArtists(SearchArtistRequest) returns (SearchArtistResponse) { option (google.api.http) = { - get: "/v1/search/artists" + get: "/api/v1/search/artists" }; } rpc SearchCollections(SearchCollectionRequest) returns (SearchCollectionResponse) { option (google.api.http) = { - get: "/v1/search/collections" + get: "/api/v1/search/collections" }; } rpc Ping(PingRequest) returns (PingResponse) { option (google.api.http) = { get: "/ping" - }; + }; } } @@ -59,6 +59,8 @@ service MusicBackend { message CollectionRequest { string name = 1; int32 index = 2; + int32 limit = 3; + int32 offset = 4; } message CollectionResponse { @@ -73,7 +75,7 @@ message SongRequest { } message SongResponse { - repeated Song songs = 1; + Song song = 1; } message ArtistRequest { @@ -112,7 +114,7 @@ message SearchSharedRequest { message SearchSharedResponse { string artist = 1; - repeated Song = 2; + repeated Song songs = 2; } message SearchArtistRequest { @@ -122,6 +124,10 @@ message SearchArtistRequest { } message SearchArtistResponse { + repeated Artist Artists = 1; +} + +message Artist { string artist = 1; int32 items = 2; } @@ -133,11 +139,15 @@ message SearchCollectionRequest { } message SearchCollectionResponse { + repeated CollectionPreview collections = 1; +} + +message CollectionPreview { string name = 1; string image = 2; int32 items = 3; -} +} //===== status ===== message PingRequest { string ping = 1; @@ -159,4 +169,4 @@ message Song { string audio = 8; int64 total_time = 9; string image = 10; -} +} \ No newline at end of file