diff --git a/main.go b/main.go index 28208af..58bf16b 100644 --- a/main.go +++ b/main.go @@ -67,6 +67,7 @@ func main() { api.HandleFunc("/auth/resend-code", authHandler.ResendVerificationCode).Methods("POST") api.HandleFunc("/auth/google/login", authHandler.GoogleLogin).Methods("GET") api.HandleFunc("/auth/google/callback", authHandler.GoogleCallback).Methods("GET") + api.HandleFunc("/auth/refresh", authHandler.RefreshToken).Methods("POST") api.HandleFunc("/search/multi", searchHandler.MultiSearch).Methods("GET") @@ -120,6 +121,8 @@ func main() { protected.HandleFunc("/auth/profile", authHandler.GetProfile).Methods("GET") protected.HandleFunc("/auth/profile", authHandler.UpdateProfile).Methods("PUT") protected.HandleFunc("/auth/profile", authHandler.DeleteAccount).Methods("DELETE") + protected.HandleFunc("/auth/revoke-token", authHandler.RevokeRefreshToken).Methods("POST") + protected.HandleFunc("/auth/revoke-all-tokens", authHandler.RevokeAllRefreshTokens).Methods("POST") protected.HandleFunc("/reactions/{mediaType}/{mediaId}/my-reaction", reactionsHandler.GetMyReaction).Methods("GET") protected.HandleFunc("/reactions/{mediaType}/{mediaId}", reactionsHandler.SetReaction).Methods("POST") diff --git a/pkg/config/vars.go b/pkg/config/vars.go index 56ac949..470f2ca 100644 --- a/pkg/config/vars.go +++ b/pkg/config/vars.go @@ -2,25 +2,25 @@ package config const ( // Environment variable keys - EnvTMDBAccessToken = "TMDB_ACCESS_TOKEN" - EnvJWTSecret = "JWT_SECRET" - EnvPort = "PORT" - EnvBaseURL = "BASE_URL" - EnvNodeEnv = "NODE_ENV" - EnvGmailUser = "GMAIL_USER" - EnvGmailPassword = "GMAIL_APP_PASSWORD" - EnvLumexURL = "LUMEX_URL" - EnvAllohaToken = "ALLOHA_TOKEN" - EnvRedAPIBaseURL = "REDAPI_BASE_URL" - EnvRedAPIKey = "REDAPI_KEY" - EnvMongoDBName = "MONGO_DB_NAME" - EnvGoogleClientID = "GOOGLE_CLIENT_ID" - EnvGoogleClientSecret= "GOOGLE_CLIENT_SECRET" - EnvGoogleRedirectURL = "GOOGLE_REDIRECT_URL" - EnvFrontendURL = "FRONTEND_URL" - EnvVibixHost = "VIBIX_HOST" - EnvVibixToken = "VIBIX_TOKEN" - + EnvTMDBAccessToken = "TMDB_ACCESS_TOKEN" + EnvJWTSecret = "JWT_SECRET" + EnvPort = "PORT" + EnvBaseURL = "BASE_URL" + EnvNodeEnv = "NODE_ENV" + EnvGmailUser = "GMAIL_USER" + EnvGmailPassword = "GMAIL_APP_PASSWORD" + EnvLumexURL = "LUMEX_URL" + EnvAllohaToken = "ALLOHA_TOKEN" + EnvRedAPIBaseURL = "REDAPI_BASE_URL" + EnvRedAPIKey = "REDAPI_KEY" + EnvMongoDBName = "MONGO_DB_NAME" + EnvGoogleClientID = "GOOGLE_CLIENT_ID" + EnvGoogleClientSecret = "GOOGLE_CLIENT_SECRET" + EnvGoogleRedirectURL = "GOOGLE_REDIRECT_URL" + EnvFrontendURL = "FRONTEND_URL" + EnvVibixHost = "VIBIX_HOST" + EnvVibixToken = "VIBIX_TOKEN" + // Default values DefaultJWTSecret = "your-secret-key" DefaultPort = "3000" @@ -28,9 +28,9 @@ const ( DefaultNodeEnv = "development" DefaultRedAPIBase = "http://redapi.cfhttp.top" DefaultMongoDBName = "database" - DefaultVibixHost = "https://vibix.org" + DefaultVibixHost = "https://vibix.org" // Static constants TMDBImageBaseURL = "https://image.tmdb.org/t/p" CubAPIBaseURL = "https://cub.rip/api" -) \ No newline at end of file +) diff --git a/pkg/database/connection.go b/pkg/database/connection.go index 9aad860..89b40a0 100644 --- a/pkg/database/connection.go +++ b/pkg/database/connection.go @@ -38,4 +38,4 @@ func Disconnect() error { return client.Disconnect(ctx) } -func GetClient() *mongo.Client { return client } \ No newline at end of file +func GetClient() *mongo.Client { return client } diff --git a/pkg/handlers/auth.go b/pkg/handlers/auth.go index 5836268..2eaa3ea 100644 --- a/pkg/handlers/auth.go +++ b/pkg/handlers/auth.go @@ -3,8 +3,8 @@ package handlers import ( "encoding/json" "net/http" - "time" "strings" + "time" "go.mongodb.org/mongo-driver/bson" @@ -46,7 +46,14 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { return } - response, err := h.authService.Login(req) + // Получаем информацию о клиенте для refresh токена + userAgent := r.Header.Get("User-Agent") + ipAddress := r.RemoteAddr + if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" { + ipAddress = forwarded + } + + response, err := h.authService.LoginWithTokens(req, userAgent, ipAddress) if err != nil { statusCode := http.StatusBadRequest if err.Error() == "Account not activated. Please verify your email." { @@ -74,7 +81,7 @@ func (h *AuthHandler) GoogleLogin(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) GoogleCallback(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() state := q.Get("state") - code := q.Get("code") + code := q.Get("code") preferJSON := q.Get("response") == "json" || strings.Contains(r.Header.Get("Accept"), "application/json") cookie, _ := r.Cookie("oauth_state") if cookie == nil || cookie.Value != state || code == "" { @@ -221,5 +228,82 @@ func (h *AuthHandler) ResendVerificationCode(w http.ResponseWriter, r *http.Requ json.NewEncoder(w).Encode(response) } +// RefreshToken refreshes an access token using a refresh token +func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) { + var req models.RefreshTokenRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // Получаем информацию о клиенте + userAgent := r.Header.Get("User-Agent") + ipAddress := r.RemoteAddr + if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" { + ipAddress = forwarded + } + + tokenPair, err := h.authService.RefreshAccessToken(req.RefreshToken, userAgent, ipAddress) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(models.APIResponse{ + Success: true, + Data: tokenPair, + Message: "Token refreshed successfully", + }) +} + +// RevokeRefreshToken revokes a specific refresh token +func (h *AuthHandler) RevokeRefreshToken(w http.ResponseWriter, r *http.Request) { + userID, ok := middleware.GetUserIDFromContext(r.Context()) + if !ok { + http.Error(w, "User ID not found in context", http.StatusInternalServerError) + return + } + + var req models.RefreshTokenRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + err := h.authService.RevokeRefreshToken(userID, req.RefreshToken) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(models.APIResponse{ + Success: true, + Message: "Refresh token revoked successfully", + }) +} + +// RevokeAllRefreshTokens revokes all refresh tokens for the current user +func (h *AuthHandler) RevokeAllRefreshTokens(w http.ResponseWriter, r *http.Request) { + userID, ok := middleware.GetUserIDFromContext(r.Context()) + if !ok { + http.Error(w, "User ID not found in context", http.StatusInternalServerError) + return + } + + err := h.authService.RevokeAllRefreshTokens(userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(models.APIResponse{ + Success: true, + Message: "All refresh tokens revoked successfully", + }) +} + // helpers -func generateState() string { return uuidNew() } \ No newline at end of file +func generateState() string { return uuidNew() } diff --git a/pkg/handlers/auth_helpers.go b/pkg/handlers/auth_helpers.go index 39c6b15..fd6f42e 100644 --- a/pkg/handlers/auth_helpers.go +++ b/pkg/handlers/auth_helpers.go @@ -4,4 +4,4 @@ import ( "github.com/google/uuid" ) -func uuidNew() string { return uuid.New().String() } \ No newline at end of file +func uuidNew() string { return uuid.New().String() } diff --git a/pkg/handlers/categories.go b/pkg/handlers/categories.go index 8b99609..d8098c1 100644 --- a/pkg/handlers/categories.go +++ b/pkg/handlers/categories.go @@ -119,4 +119,4 @@ func generateSlug(name string) string { } } return result -} \ No newline at end of file +} diff --git a/pkg/handlers/docs.go b/pkg/handlers/docs.go index 38c1d0c..1aec0d2 100644 --- a/pkg/handlers/docs.go +++ b/pkg/handlers/docs.go @@ -113,11 +113,11 @@ func determineBaseURL(r *http.Request) string { } type OpenAPISpec struct { - OpenAPI string `json:"openapi"` - Info Info `json:"info"` - Servers []Server `json:"servers"` - Paths map[string]interface{} `json:"paths"` - Components Components `json:"components"` + OpenAPI string `json:"openapi"` + Info Info `json:"info"` + Servers []Server `json:"servers"` + Paths map[string]interface{} `json:"paths"` + Components Components `json:"components"` } type Info struct { @@ -169,9 +169,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { Paths: map[string]interface{}{ "/api/v1/health": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Health Check", + "summary": "Health Check", "description": "Проверка работоспособности API", - "tags": []string{"Health"}, + "tags": []string{"Health"}, "responses": map[string]interface{}{ "200": map[string]interface{}{ "description": "API работает корректно", @@ -188,21 +188,21 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/search/multi": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Мультипоиск", + "summary": "Мультипоиск", "description": "Поиск фильмов, сериалов и актеров", - "tags": []string{"Search"}, + "tags": []string{"Search"}, "parameters": []map[string]interface{}{ { - "name": "query", - "in": "query", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "query", + "in": "query", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Поисковый запрос", }, { - "name": "page", - "in": "query", - "schema": map[string]string{"type": "integer", "default": "1"}, + "name": "page", + "in": "query", + "schema": map[string]string{"type": "integer", "default": "1"}, "description": "Номер страницы", }, }, @@ -215,16 +215,16 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/categories": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Получить категории", + "summary": "Получить категории", "description": "Получение списка категорий фильмов", - "tags": []string{"Categories"}, + "tags": []string{"Categories"}, "responses": map[string]interface{}{ "200": map[string]interface{}{ "description": "Список категорий", "content": map[string]interface{}{ "application/json": map[string]interface{}{ "schema": map[string]interface{}{ - "type": "array", + "type": "array", "items": map[string]interface{}{"$ref": "#/components/schemas/Category"}, }, }, @@ -235,15 +235,15 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/categories/{id}/movies": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Фильмы по категории", + "summary": "Фильмы по категории", "description": "Получение фильмов по категории", - "tags": []string{"Categories"}, + "tags": []string{"Categories"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID категории", }, }, @@ -256,44 +256,44 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/categories/{id}/media": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Медиа по категории", + "summary": "Медиа по категории", "description": "Получение фильмов или сериалов по категории", - "tags": []string{"Categories"}, + "tags": []string{"Categories"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID категории", }, { - "name": "type", - "in": "query", + "name": "type", + "in": "query", "required": false, "schema": map[string]interface{}{ - "type": "string", - "enum": []string{"movie", "tv"}, + "type": "string", + "enum": []string{"movie", "tv"}, "default": "movie", }, "description": "Тип медиа: movie или tv", }, { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "required": false, "schema": map[string]interface{}{ - "type": "integer", + "type": "integer", "default": 1, }, "description": "Номер страницы", }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "required": false, "schema": map[string]interface{}{ - "type": "string", + "type": "string", "default": "ru-RU", }, "description": "Язык ответа", @@ -308,15 +308,15 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/players/alloha/{imdb_id}": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Плеер Alloha", + "summary": "Плеер Alloha", "description": "Получение плеера Alloha по IMDb ID", - "tags": []string{"Players"}, + "tags": []string{"Players"}, "parameters": []map[string]interface{}{ { - "name": "imdb_id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "imdb_id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "IMDb ID фильма", }, }, @@ -329,15 +329,15 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/players/lumex/{imdb_id}": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Плеер Lumex", + "summary": "Плеер Lumex", "description": "Получение плеера Lumex по IMDb ID", - "tags": []string{"Players"}, + "tags": []string{"Players"}, "parameters": []map[string]interface{}{ { - "name": "imdb_id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "imdb_id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "IMDb ID фильма", }, }, @@ -376,22 +376,22 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/webtorrent/player": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "WebTorrent плеер", + "summary": "WebTorrent плеер", "description": "Открытие WebTorrent плеера с магнет ссылкой. Плеер работает полностью на стороне клиента.", - "tags": []string{"WebTorrent"}, + "tags": []string{"WebTorrent"}, "parameters": []map[string]interface{}{ { - "name": "magnet", - "in": "query", - "required": false, - "schema": map[string]string{"type": "string"}, + "name": "magnet", + "in": "query", + "required": false, + "schema": map[string]string{"type": "string"}, "description": "Магнет ссылка торрента", }, { - "name": "X-Magnet-Link", - "in": "header", - "required": false, - "schema": map[string]string{"type": "string"}, + "name": "X-Magnet-Link", + "in": "header", + "required": false, + "schema": map[string]string{"type": "string"}, "description": "Магнет ссылка через заголовок (альтернативный способ)", }, }, @@ -412,15 +412,15 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/webtorrent/metadata": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Метаданные медиа", + "summary": "Метаданные медиа", "description": "Получение метаданных фильма или сериала по названию для WebTorrent плеера", - "tags": []string{"WebTorrent"}, + "tags": []string{"WebTorrent"}, "parameters": []map[string]interface{}{ { - "name": "query", - "in": "query", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "query", + "in": "query", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Название для поиска (извлеченное из торрента)", }, }, @@ -481,22 +481,22 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/reactions/{mediaType}/{mediaId}/counts": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Количество реакций", + "summary": "Количество реакций", "description": "Получение количества реакций для медиа", - "tags": []string{"Reactions"}, + "tags": []string{"Reactions"}, "parameters": []map[string]interface{}{ { - "name": "mediaType", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "mediaType", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Тип медиа (movie/tv)", }, { - "name": "mediaId", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "mediaId", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "ID медиа", }, }, @@ -516,22 +516,22 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/images/{size}/{path}": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Изображения", + "summary": "Изображения", "description": "Прокси для изображений TMDB", - "tags": []string{"Images"}, + "tags": []string{"Images"}, "parameters": []map[string]interface{}{ { - "name": "size", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "size", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Размер изображения", }, { - "name": "path", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "path", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Путь к изображению", }, }, @@ -547,9 +547,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/auth/register": map[string]interface{}{ "post": map[string]interface{}{ - "summary": "Регистрация пользователя", + "summary": "Регистрация пользователя", "description": "Создание нового аккаунта пользователя", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "requestBody": map[string]interface{}{ "required": true, "content": map[string]interface{}{ @@ -587,7 +587,7 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "content": map[string]interface{}{ "application/json": map[string]interface{}{ "schema": map[string]interface{}{ - "type": "object", + "type": "object", "required": []string{"email", "code"}, "properties": map[string]interface{}{ "email": map[string]interface{}{ @@ -641,7 +641,7 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "content": map[string]interface{}{ "application/json": map[string]interface{}{ "schema": map[string]interface{}{ - "type": "object", + "type": "object", "required": []string{"email"}, "properties": map[string]interface{}{ "email": map[string]interface{}{ @@ -682,9 +682,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/auth/login": map[string]interface{}{ "post": map[string]interface{}{ - "summary": "Авторизация пользователя", + "summary": "Авторизация пользователя", "description": "Получение JWT токена для доступа к приватным эндпоинтам", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "requestBody": map[string]interface{}{ "required": true, "content": map[string]interface{}{ @@ -714,9 +714,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/auth/profile": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Получить профиль пользователя", + "summary": "Получить профиль пользователя", "description": "Получение информации о текущем пользователе", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, @@ -734,9 +734,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, }, "put": map[string]interface{}{ - "summary": "Обновить профиль пользователя", + "summary": "Обновить профиль пользователя", "description": "Обновление информации о пользователе", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, @@ -747,9 +747,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, }, "delete": map[string]interface{}{ - "summary": "Удалить аккаунт пользователя", + "summary": "Удалить аккаунт пользователя", "description": "Полное и безвозвратное удаление аккаунта пользователя и всех связанных с ним данных (избранное, реакции)", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, @@ -779,33 +779,33 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/search": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Поиск фильмов", + "summary": "Поиск фильмов", "description": "Поиск фильмов по названию с поддержкой фильтров", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "query", - "in": "query", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "query", + "in": "query", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Поисковый запрос", }, { - "name": "page", - "in": "query", - "schema": map[string]string{"type": "integer", "default": "1"}, + "name": "page", + "in": "query", + "schema": map[string]string{"type": "integer", "default": "1"}, "description": "Номер страницы", }, { - "name": "language", - "in": "query", - "schema": map[string]string{"type": "string", "default": "ru-RU"}, + "name": "language", + "in": "query", + "schema": map[string]string{"type": "string", "default": "ru-RU"}, "description": "Язык ответа", }, { - "name": "year", - "in": "query", - "schema": map[string]string{"type": "integer"}, + "name": "year", + "in": "query", + "schema": map[string]string{"type": "integer"}, "description": "Год выпуска", }, }, @@ -825,18 +825,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/popular": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Популярные фильмы", + "summary": "Популярные фильмы", "description": "Получение списка популярных фильмов", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -849,20 +849,20 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/{id}": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Получить фильм по ID", + "summary": "Получить фильм по ID", "description": "Подробная информация о фильме", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID фильма в TMDB", }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -882,9 +882,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/favorites": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Получить избранное", + "summary": "Получить избранное", "description": "Список избранных фильмов и сериалов пользователя", - "tags": []string{"Favorites"}, + "tags": []string{"Favorites"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, @@ -897,27 +897,27 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/favorites/{id}": map[string]interface{}{ "post": map[string]interface{}{ - "summary": "Добавить в избранное", + "summary": "Добавить в избранное", "description": "Добавление фильма или сериала в избранное", - "tags": []string{"Favorites"}, + "tags": []string{"Favorites"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "ID медиа", }, { - "name": "type", - "in": "query", + "name": "type", + "in": "query", "required": false, "schema": map[string]interface{}{ - "type": "string", - "enum": []string{"movie", "tv"}, + "type": "string", + "enum": []string{"movie", "tv"}, "default": "movie", }, "description": "Тип медиа: movie или tv", @@ -930,27 +930,27 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, }, "delete": map[string]interface{}{ - "summary": "Удалить из избранного", + "summary": "Удалить из избранного", "description": "Удаление фильма или сериала из избранного", - "tags": []string{"Favorites"}, + "tags": []string{"Favorites"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "ID медиа", }, { - "name": "type", - "in": "query", + "name": "type", + "in": "query", "required": false, "schema": map[string]interface{}{ - "type": "string", - "enum": []string{"movie", "tv"}, + "type": "string", + "enum": []string{"movie", "tv"}, "default": "movie", }, "description": "Тип медиа: movie или tv", @@ -965,27 +965,27 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/favorites/{id}/check": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Проверить избранное", + "summary": "Проверить избранное", "description": "Проверка, находится ли медиа в избранном", - "tags": []string{"Favorites"}, + "tags": []string{"Favorites"}, "security": []map[string][]string{ {"bearerAuth": []string{}}, }, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "ID медиа", }, { - "name": "type", - "in": "query", + "name": "type", + "in": "query", "required": false, "schema": map[string]interface{}{ - "type": "string", - "enum": []string{"movie", "tv"}, + "type": "string", + "enum": []string{"movie", "tv"}, "default": "movie", }, "description": "Тип медиа: movie или tv", @@ -1000,18 +1000,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/top-rated": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Топ рейтинг фильмов", + "summary": "Топ рейтинг фильмов", "description": "Получение списка фильмов с высоким рейтингом", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1024,18 +1024,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/upcoming": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Скоро в прокате", + "summary": "Скоро в прокате", "description": "Получение списка фильмов, которые скоро выйдут в прокат", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1048,18 +1048,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/now-playing": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Сейчас в прокате", + "summary": "Сейчас в прокате", "description": "Получение списка фильмов, которые сейчас в прокате", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1072,25 +1072,25 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/{id}/recommendations": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Рекомендации фильмов", + "summary": "Рекомендации фильмов", "description": "Получение рекомендаций фильмов на основе выбранного", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID фильма в TMDB", }, { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1103,25 +1103,25 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/{id}/similar": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Похожие фильмы", + "summary": "Похожие фильмы", "description": "Получение похожих фильмов", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID фильма в TMDB", }, { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1134,27 +1134,27 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/search": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Поиск сериалов", + "summary": "Поиск сериалов", "description": "Поиск сериалов по названию", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "query", - "in": "query", - "required": true, - "schema": map[string]string{"type": "string"}, + "name": "query", + "in": "query", + "required": true, + "schema": map[string]string{"type": "string"}, "description": "Поисковый запрос", }, { - "name": "page", - "in": "query", - "schema": map[string]string{"type": "integer", "default": "1"}, + "name": "page", + "in": "query", + "schema": map[string]string{"type": "integer", "default": "1"}, "description": "Номер страницы", }, { - "name": "language", - "in": "query", - "schema": map[string]string{"type": "string", "default": "ru-RU"}, + "name": "language", + "in": "query", + "schema": map[string]string{"type": "string", "default": "ru-RU"}, "description": "Язык ответа", }, }, @@ -1174,18 +1174,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/popular": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Популярные сериалы", + "summary": "Популярные сериалы", "description": "Получение списка популярных сериалов", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1198,18 +1198,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/top-rated": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Топ рейтинг сериалов", + "summary": "Топ рейтинг сериалов", "description": "Получение списка сериалов с высоким рейтингом", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1222,18 +1222,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/on-the-air": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "В эфире", + "summary": "В эфире", "description": "Получение списка сериалов, которые сейчас в эфире", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1246,18 +1246,18 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/airing-today": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Сегодня в эфире", + "summary": "Сегодня в эфире", "description": "Получение списка сериалов, которые выходят сегодня", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1270,20 +1270,20 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/{id}": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Получить сериал по ID", + "summary": "Получить сериал по ID", "description": "Подробная информация о сериале", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID сериала в TMDB", }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1303,25 +1303,25 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/{id}/recommendations": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Рекомендации сериалов", + "summary": "Рекомендации сериалов", "description": "Получение рекомендаций сериалов на основе выбранного", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID сериала в TMDB", }, { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1334,25 +1334,25 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/{id}/similar": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Похожие сериалы", + "summary": "Похожие сериалы", "description": "Получение похожих сериалов", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID сериала в TMDB", }, { - "name": "page", - "in": "query", + "name": "page", + "in": "query", "schema": map[string]string{"type": "integer", "default": "1"}, }, { - "name": "language", - "in": "query", + "name": "language", + "in": "query", "schema": map[string]string{"type": "string", "default": "ru-RU"}, }, }, @@ -1365,15 +1365,15 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/movies/{id}/external-ids": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Внешние идентификаторы фильма", + "summary": "Внешние идентификаторы фильма", "description": "Получить внешние ID (IMDb, TVDB, Facebook и др.) для фильма по TMDB ID", - "tags": []string{"Movies"}, + "tags": []string{"Movies"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID фильма в TMDB", }, }, @@ -1393,15 +1393,15 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/tv/{id}/external-ids": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Внешние идентификаторы сериала", + "summary": "Внешние идентификаторы сериала", "description": "Получить внешние ID (IMDb, TVDB, Facebook и др.) для сериала по TMDB ID", - "tags": []string{"TV Series"}, + "tags": []string{"TV Series"}, "parameters": []map[string]interface{}{ { - "name": "id", - "in": "path", - "required": true, - "schema": map[string]string{"type": "integer"}, + "name": "id", + "in": "path", + "required": true, + "schema": map[string]string{"type": "integer"}, "description": "ID сериала в TMDB", }, }, @@ -1421,9 +1421,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/auth/google/login": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Google OAuth: начало", + "summary": "Google OAuth: начало", "description": "Редирект на страницу авторизации Google", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "responses": map[string]interface{}{ "302": map[string]interface{}{"description": "Redirect to Google"}, "400": map[string]interface{}{"description": "OAuth не сконфигурирован"}, @@ -1432,9 +1432,9 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { }, "/api/v1/auth/google/callback": map[string]interface{}{ "get": map[string]interface{}{ - "summary": "Google OAuth: коллбек", + "summary": "Google OAuth: коллбек", "description": "Обработка кода авторизации и выдача JWT", - "tags": []string{"Authentication"}, + "tags": []string{"Authentication"}, "parameters": []map[string]interface{}{ {"name": "state", "in": "query", "required": true, "schema": map[string]string{"type": "string"}}, {"name": "code", "in": "query", "required": true, "schema": map[string]string{"type": "string"}}, @@ -1466,42 +1466,42 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "type": "object", "properties": map[string]interface{}{ "success": map[string]string{"type": "boolean"}, - "data": map[string]string{"type": "object"}, + "data": map[string]string{"type": "object"}, "message": map[string]string{"type": "string"}, - "error": map[string]string{"type": "string"}, + "error": map[string]string{"type": "string"}, }, }, "RegisterRequest": map[string]interface{}{ - "type": "object", + "type": "object", "required": []string{"email", "password", "name"}, "properties": map[string]interface{}{ "email": map[string]interface{}{ - "type": "string", - "format": "email", + "type": "string", + "format": "email", "example": "user@example.com", }, "password": map[string]interface{}{ - "type": "string", + "type": "string", "minLength": 6, - "example": "password123", + "example": "password123", }, "name": map[string]interface{}{ - "type": "string", + "type": "string", "example": "Иван Иванов", }, }, }, "LoginRequest": map[string]interface{}{ - "type": "object", + "type": "object", "required": []string{"email", "password"}, "properties": map[string]interface{}{ "email": map[string]interface{}{ - "type": "string", - "format": "email", + "type": "string", + "format": "email", "example": "user@example.com", }, "password": map[string]interface{}{ - "type": "string", + "type": "string", "example": "password123", }, }, @@ -1510,26 +1510,26 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "type": "object", "properties": map[string]interface{}{ "token": map[string]string{"type": "string"}, - "user": map[string]interface{}{"$ref": "#/components/schemas/User"}, + "user": map[string]interface{}{"$ref": "#/components/schemas/User"}, }, }, "User": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "string"}, - "email": map[string]string{"type": "string"}, - "name": map[string]string{"type": "string"}, + "id": map[string]string{"type": "string"}, + "email": map[string]string{"type": "string"}, + "name": map[string]string{"type": "string"}, "avatar": map[string]string{"type": "string"}, "favorites": map[string]interface{}{ - "type": "array", + "type": "array", "items": map[string]string{"type": "string"}, }, "created_at": map[string]interface{}{ - "type": "string", + "type": "string", "format": "date-time", }, "updated_at": map[string]interface{}{ - "type": "string", + "type": "string", "format": "date-time", }, }, @@ -1537,17 +1537,17 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "Movie": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "integer"}, - "title": map[string]string{"type": "string"}, - "original_title": map[string]string{"type": "string"}, - "overview": map[string]string{"type": "string"}, - "poster_path": map[string]string{"type": "string"}, - "backdrop_path": map[string]string{"type": "string"}, - "release_date": map[string]string{"type": "string"}, - "vote_average": map[string]string{"type": "number"}, - "vote_count": map[string]string{"type": "integer"}, - "popularity": map[string]string{"type": "number"}, - "adult": map[string]string{"type": "boolean"}, + "id": map[string]string{"type": "integer"}, + "title": map[string]string{"type": "string"}, + "original_title": map[string]string{"type": "string"}, + "overview": map[string]string{"type": "string"}, + "poster_path": map[string]string{"type": "string"}, + "backdrop_path": map[string]string{"type": "string"}, + "release_date": map[string]string{"type": "string"}, + "vote_average": map[string]string{"type": "number"}, + "vote_count": map[string]string{"type": "integer"}, + "popularity": map[string]string{"type": "number"}, + "adult": map[string]string{"type": "boolean"}, "original_language": map[string]string{"type": "string"}, }, }, @@ -1556,30 +1556,30 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "properties": map[string]interface{}{ "page": map[string]string{"type": "integer"}, "results": map[string]interface{}{ - "type": "array", + "type": "array", "items": map[string]interface{}{"$ref": "#/components/schemas/Movie"}, }, - "total_pages": map[string]string{"type": "integer"}, + "total_pages": map[string]string{"type": "integer"}, "total_results": map[string]string{"type": "integer"}, }, }, "TVSeries": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "integer"}, - "name": map[string]string{"type": "string"}, - "original_name": map[string]string{"type": "string"}, - "overview": map[string]string{"type": "string"}, - "poster_path": map[string]string{"type": "string"}, - "backdrop_path": map[string]string{"type": "string"}, - "first_air_date": map[string]string{"type": "string"}, - "vote_average": map[string]string{"type": "number"}, - "vote_count": map[string]string{"type": "integer"}, - "popularity": map[string]string{"type": "number"}, - "original_language": map[string]string{"type": "string"}, - "number_of_seasons": map[string]string{"type": "integer"}, + "id": map[string]string{"type": "integer"}, + "name": map[string]string{"type": "string"}, + "original_name": map[string]string{"type": "string"}, + "overview": map[string]string{"type": "string"}, + "poster_path": map[string]string{"type": "string"}, + "backdrop_path": map[string]string{"type": "string"}, + "first_air_date": map[string]string{"type": "string"}, + "vote_average": map[string]string{"type": "number"}, + "vote_count": map[string]string{"type": "integer"}, + "popularity": map[string]string{"type": "number"}, + "original_language": map[string]string{"type": "string"}, + "number_of_seasons": map[string]string{"type": "integer"}, "number_of_episodes": map[string]string{"type": "integer"}, - "status": map[string]string{"type": "string"}, + "status": map[string]string{"type": "string"}, }, }, "TVSearchResponse": map[string]interface{}{ @@ -1587,82 +1587,82 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "properties": map[string]interface{}{ "page": map[string]string{"type": "integer"}, "results": map[string]interface{}{ - "type": "array", + "type": "array", "items": map[string]interface{}{"$ref": "#/components/schemas/TVSeries"}, }, - "total_pages": map[string]string{"type": "integer"}, + "total_pages": map[string]string{"type": "integer"}, "total_results": map[string]string{"type": "integer"}, }, }, "Category": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "integer"}, - "name": map[string]string{"type": "string"}, + "id": map[string]string{"type": "integer"}, + "name": map[string]string{"type": "string"}, "description": map[string]string{"type": "string"}, }, }, "Player": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "url": map[string]string{"type": "string"}, - "title": map[string]string{"type": "string"}, + "url": map[string]string{"type": "string"}, + "title": map[string]string{"type": "string"}, "quality": map[string]string{"type": "string"}, - "type": map[string]string{"type": "string"}, + "type": map[string]string{"type": "string"}, }, }, "Torrent": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "title": map[string]string{"type": "string"}, - "size": map[string]string{"type": "string"}, - "seeds": map[string]string{"type": "integer"}, - "peers": map[string]string{"type": "integer"}, + "title": map[string]string{"type": "string"}, + "size": map[string]string{"type": "string"}, + "seeds": map[string]string{"type": "integer"}, + "peers": map[string]string{"type": "integer"}, "magnet": map[string]string{"type": "string"}, - "hash": map[string]string{"type": "string"}, + "hash": map[string]string{"type": "string"}, }, }, "Reaction": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "type": map[string]string{"type": "string"}, + "type": map[string]string{"type": "string"}, "count": map[string]string{"type": "integer"}, }, }, "ReactionCounts": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "like": map[string]string{"type": "integer"}, + "like": map[string]string{"type": "integer"}, "dislike": map[string]string{"type": "integer"}, - "love": map[string]string{"type": "integer"}, + "love": map[string]string{"type": "integer"}, }, }, "ExternalIDs": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "integer"}, - "imdb_id": map[string]string{"type": "string"}, - "tvdb_id": map[string]string{"type": "integer"}, - "wikidata_id": map[string]string{"type": "string"}, - "facebook_id": map[string]string{"type": "string"}, + "id": map[string]string{"type": "integer"}, + "imdb_id": map[string]string{"type": "string"}, + "tvdb_id": map[string]string{"type": "integer"}, + "wikidata_id": map[string]string{"type": "string"}, + "facebook_id": map[string]string{"type": "string"}, "instagram_id": map[string]string{"type": "string"}, - "twitter_id": map[string]string{"type": "string"}, + "twitter_id": map[string]string{"type": "string"}, }, }, "WebTorrentMetadata": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "integer"}, + "id": map[string]string{"type": "integer"}, "title": map[string]string{"type": "string"}, "type": map[string]interface{}{ "type": "string", "enum": []string{"movie", "tv"}, }, - "year": map[string]string{"type": "integer"}, - "posterPath": map[string]string{"type": "string"}, + "year": map[string]string{"type": "integer"}, + "posterPath": map[string]string{"type": "string"}, "backdropPath": map[string]string{"type": "string"}, - "overview": map[string]string{"type": "string"}, - "runtime": map[string]string{"type": "integer"}, + "overview": map[string]string{"type": "string"}, + "runtime": map[string]string{"type": "integer"}, "genres": map[string]interface{}{ "type": "array", "items": map[string]interface{}{ @@ -1687,7 +1687,7 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "type": "object", "properties": map[string]interface{}{ "seasonNumber": map[string]string{"type": "integer"}, - "name": map[string]string{"type": "string"}, + "name": map[string]string{"type": "string"}, "episodes": map[string]interface{}{ "type": "array", "items": map[string]interface{}{ @@ -1700,17 +1700,17 @@ func getOpenAPISpecWithURL(baseURL string) *OpenAPISpec { "type": "object", "properties": map[string]interface{}{ "episodeNumber": map[string]string{"type": "integer"}, - "seasonNumber": map[string]string{"type": "integer"}, - "name": map[string]string{"type": "string"}, - "overview": map[string]string{"type": "string"}, - "runtime": map[string]string{"type": "integer"}, - "stillPath": map[string]string{"type": "string"}, + "seasonNumber": map[string]string{"type": "integer"}, + "name": map[string]string{"type": "string"}, + "overview": map[string]string{"type": "string"}, + "runtime": map[string]string{"type": "integer"}, + "stillPath": map[string]string{"type": "string"}, }, }, "Genre": map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ - "id": map[string]string{"type": "integer"}, + "id": map[string]string{"type": "integer"}, "name": map[string]string{"type": "string"}, }, }, diff --git a/pkg/handlers/health.go b/pkg/handlers/health.go index 0470a78..dafbec7 100644 --- a/pkg/handlers/health.go +++ b/pkg/handlers/health.go @@ -26,4 +26,4 @@ func HealthCheck(w http.ResponseWriter, r *http.Request) { }) } -var startTime = time.Now() \ No newline at end of file +var startTime = time.Now() diff --git a/pkg/handlers/images.go b/pkg/handlers/images.go index 47821f8..12d744f 100644 --- a/pkg/handlers/images.go +++ b/pkg/handlers/images.go @@ -131,4 +131,4 @@ func (h *ImagesHandler) isValidSize(size string, validSizes []string) bool { } } return false -} \ No newline at end of file +} diff --git a/pkg/handlers/movie.go b/pkg/handlers/movie.go index 3543064..26f59d9 100644 --- a/pkg/handlers/movie.go +++ b/pkg/handlers/movie.go @@ -189,8 +189,6 @@ func (h *MovieHandler) GetSimilar(w http.ResponseWriter, r *http.Request) { }) } - - func (h *MovieHandler) GetExternalIDs(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) @@ -217,11 +215,11 @@ func getIntQuery(r *http.Request, key string, defaultValue int) int { if str == "" { return defaultValue } - + value, err := strconv.Atoi(str) if err != nil { return defaultValue } - + return value -} \ No newline at end of file +} diff --git a/pkg/handlers/players.go b/pkg/handlers/players.go index 682b2f9..9f9e8ac 100644 --- a/pkg/handlers/players.go +++ b/pkg/handlers/players.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "neomovies-api/pkg/config" "github.com/gorilla/mux" + "neomovies-api/pkg/config" ) type PlayersHandler struct { @@ -26,29 +26,29 @@ func NewPlayersHandler(cfg *config.Config) *PlayersHandler { func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request) { log.Printf("GetAllohaPlayer called: %s %s", r.Method, r.URL.Path) - + vars := mux.Vars(r) log.Printf("Route vars: %+v", vars) - + imdbID := vars["imdb_id"] if imdbID == "" { log.Printf("Error: imdb_id is empty") http.Error(w, "imdb_id path param is required", http.StatusBadRequest) return } - + log.Printf("Processing imdb_id: %s", imdbID) - + if h.config.AllohaToken == "" { log.Printf("Error: ALLOHA_TOKEN is missing") http.Error(w, "Server misconfiguration: ALLOHA_TOKEN missing", http.StatusInternalServerError) return } - + idParam := fmt.Sprintf("imdb=%s", url.QueryEscape(imdbID)) apiURL := fmt.Sprintf("https://api.alloha.tv/?token=%s&%s", h.config.AllohaToken, idParam) log.Printf("Calling Alloha API: %s", apiURL) - + resp, err := http.Get(apiURL) if err != nil { log.Printf("Error calling Alloha API: %v", err) @@ -56,105 +56,105 @@ func (h *PlayersHandler) GetAllohaPlayer(w http.ResponseWriter, r *http.Request) return } defer resp.Body.Close() - + log.Printf("Alloha API response status: %d", resp.StatusCode) - + if resp.StatusCode != http.StatusOK { http.Error(w, fmt.Sprintf("Alloha API error: %d", resp.StatusCode), http.StatusBadGateway) return } - + body, err := io.ReadAll(resp.Body) if err != nil { log.Printf("Error reading Alloha response: %v", err) http.Error(w, "Failed to read Alloha response", http.StatusInternalServerError) return } - + log.Printf("Alloha API response body: %s", string(body)) - + var allohaResponse struct { Status string `json:"status"` - Data struct { + Data struct { Iframe string `json:"iframe"` } `json:"data"` } - + if err := json.Unmarshal(body, &allohaResponse); err != nil { log.Printf("Error unmarshaling JSON: %v", err) http.Error(w, "Invalid JSON from Alloha", http.StatusBadGateway) return } - + if allohaResponse.Status != "success" || allohaResponse.Data.Iframe == "" { log.Printf("Video not found or empty iframe") http.Error(w, "Video not found", http.StatusNotFound) return } - + iframeCode := allohaResponse.Data.Iframe if !strings.Contains(iframeCode, "<") { iframeCode = fmt.Sprintf(``, iframeCode) } - + htmlDoc := fmt.Sprintf(`