mirror of
https://gitlab.com/foxixus/neomovies-api.git
synced 2025-10-28 01:48:51 +05:00
Update 25 files
- /docs/docs.go - /docs/swagger.json - /docs/swagger.yaml - /internal/api/handlers.go - /internal/api/init.go - /internal/api/models.go - /internal/api/utils.go - /internal/tmdb/client.go - /internal/tmdb/models.go - /src/config/tmdb.js - /src/routes/movies.js - /src/utils/date.js - /src/utils/health.js - /src/index.js - /build.sh - /clean.sh - /go.mod - /go.sum - /main.go - /package-lock.json - /package.json - /README.md - /render.yaml - /run.sh - /vercel.json
This commit is contained in:
85
README.md
85
README.md
@@ -1,85 +0,0 @@
|
||||
# Neo Movies API
|
||||
|
||||
REST API для поиска и получения информации о фильмах, использующий TMDB API.
|
||||
|
||||
## Особенности
|
||||
|
||||
- Поиск фильмов
|
||||
- Информация о фильмах
|
||||
- Популярные фильмы
|
||||
- Топ рейтинговые фильмы
|
||||
- Предстоящие фильмы
|
||||
- Swagger документация
|
||||
- Поддержка русского языка
|
||||
|
||||
## Установка
|
||||
|
||||
1. Клонируйте репозиторий:
|
||||
```bash
|
||||
git clone https://github.com/yourusername/neomovies-api.git
|
||||
cd neomovies-api
|
||||
```
|
||||
|
||||
2. Установите зависимости:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Создайте файл `.env` на основе `.env.example`:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
4. Добавьте ваш TMDB Access Token в `.env` файл:
|
||||
```
|
||||
TMDB_ACCESS_TOKEN=your_token_here
|
||||
```
|
||||
|
||||
## Запуск
|
||||
|
||||
Для разработки:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Для продакшена:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## Развертывание на Vercel
|
||||
|
||||
1. Установите Vercel CLI:
|
||||
```bash
|
||||
npm i -g vercel
|
||||
```
|
||||
|
||||
2. Войдите в ваш аккаунт Vercel:
|
||||
```bash
|
||||
vercel login
|
||||
```
|
||||
|
||||
3. Разверните приложение:
|
||||
```bash
|
||||
vercel
|
||||
```
|
||||
|
||||
4. Добавьте переменные окружения в Vercel:
|
||||
- Перейдите в настройки проекта на Vercel
|
||||
- Добавьте `TMDB_ACCESS_TOKEN` в раздел Environment Variables
|
||||
|
||||
## API Endpoints
|
||||
|
||||
- `GET /health` - Проверка работоспособности API
|
||||
- `GET /movies/search?query=<search_term>&page=<page_number>` - Поиск фильмов
|
||||
- `GET /movies/:id` - Получить информацию о фильме
|
||||
- `GET /movies/popular` - Получить список популярных фильмов
|
||||
- `GET /movies/top-rated` - Получить список топ рейтинговых фильмов
|
||||
- `GET /movies/upcoming` - Получить список предстоящих фильмов
|
||||
- `GET /movies/:id/external-ids` - Получить внешние ID фильма
|
||||
|
||||
## Документация API
|
||||
|
||||
После запуска API, документация Swagger доступна по адресу:
|
||||
```
|
||||
http://localhost:3000/api-docs
|
||||
26
build.sh
26
build.sh
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Создаем директорию для сборки
|
||||
BUILD_DIR="$HOME/build_tmp"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Скачиваем и устанавливаем Go во временную директорию
|
||||
curl -L https://go.dev/dl/go1.21.5.linux-amd64.tar.gz | tar -C "$BUILD_DIR" -xz
|
||||
|
||||
# Настраиваем переменные окружения для Go
|
||||
export PATH="$BUILD_DIR/go/bin:$PATH"
|
||||
export GOPATH="$BUILD_DIR/go_path"
|
||||
export GOCACHE="$BUILD_DIR/go-build"
|
||||
export GOMODCACHE="$BUILD_DIR/go-mod"
|
||||
|
||||
# Создаем необходимые директории
|
||||
mkdir -p "$GOPATH"
|
||||
mkdir -p "$GOCACHE"
|
||||
mkdir -p "$GOMODCACHE"
|
||||
|
||||
# Собираем приложение с отключенным CGO и уменьшенным бинарником
|
||||
cd "$HOME/neomovies-api"
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o app
|
||||
|
||||
# Очищаем после сборки
|
||||
rm -rf "$BUILD_DIR"
|
||||
13
clean.sh
13
clean.sh
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Очищаем кэш Go
|
||||
rm -rf $HOME/go/pkg/*
|
||||
rm -rf $HOME/.cache/go-build/*
|
||||
|
||||
# Удаляем временные файлы
|
||||
rm -f go1.21.5.linux-amd64.tar.gz
|
||||
rm -rf $HOME/go/src/*
|
||||
|
||||
# Очищаем ненужные файлы в проекте
|
||||
rm -rf vendor/
|
||||
rm -f app
|
||||
806
docs/docs.go
806
docs/docs.go
@@ -1,806 +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": {
|
||||
"/bridge/tmdb/discover/movie": {
|
||||
"get": {
|
||||
"description": "Get a list of movies based on filters",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Discover movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/discover/tv": {
|
||||
"get": {
|
||||
"description": "Get a list of TV shows based on filters",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Discover TV shows",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/popular": {
|
||||
"get": {
|
||||
"description": "Get a list of popular movies directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB popular movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/top_rated": {
|
||||
"get": {
|
||||
"description": "Get a list of top rated movies directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB top rated movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/upcoming": {
|
||||
"get": {
|
||||
"description": "Get a list of upcoming movies directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB upcoming movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/{id}": {
|
||||
"get": {
|
||||
"description": "Get detailed information about a specific movie directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB movie details",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Movie ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/{id}/external_ids": {
|
||||
"get": {
|
||||
"description": "Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific movie",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB movie external IDs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Movie ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.ExternalIDs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/search/movie": {
|
||||
"get": {
|
||||
"description": "Search for movies directly in TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Search TMDB movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/search/tv": {
|
||||
"get": {
|
||||
"description": "Search for TV shows directly in TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Search TMDB TV shows",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.TVSearchResults"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/tv/{id}/external_ids": {
|
||||
"get": {
|
||||
"description": "Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific TV show",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB TV show external IDs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "TV Show ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.ExternalIDs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/popular": {
|
||||
"get": {
|
||||
"description": "Get a list of popular movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get popular movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/search": {
|
||||
"get": {
|
||||
"description": "Search for movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Search movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/top-rated": {
|
||||
"get": {
|
||||
"description": "Get a list of top rated movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get top rated movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/upcoming": {
|
||||
"get": {
|
||||
"description": "Get a list of upcoming movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get upcoming movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/{id}": {
|
||||
"get": {
|
||||
"description": "Get detailed information about a specific movie",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get movie details",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Movie ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MovieDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"api.Genre": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.Movie": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Genre"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.MovieDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"budget": {
|
||||
"type": "integer"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Genre"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"revenue": {
|
||||
"type": "integer"
|
||||
},
|
||||
"runtime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"tagline": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.MoviesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Movie"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.TMDBMoviesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Movie"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.ExternalIDs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"facebook_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"imdb_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"instagram_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"twitter_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.Genre": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.Movie": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tmdb.Genre"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.MoviesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tmdb.Movie"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.TV": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_air_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"genre_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"original_language": {
|
||||
"type": "string"
|
||||
},
|
||||
"original_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"popularity": {
|
||||
"type": "number"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
},
|
||||
"vote_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.TVSearchResults": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tmdb.TV"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Host: "localhost:8080",
|
||||
BasePath: "/",
|
||||
Schemes: []string{},
|
||||
Title: "Neo Movies API",
|
||||
Description: "API для работы с фильмами",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
@@ -1,782 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "API для работы с фильмами",
|
||||
"title": "Neo Movies API",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/",
|
||||
"paths": {
|
||||
"/bridge/tmdb/discover/movie": {
|
||||
"get": {
|
||||
"description": "Get a list of movies based on filters",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Discover movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/discover/tv": {
|
||||
"get": {
|
||||
"description": "Get a list of TV shows based on filters",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Discover TV shows",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/popular": {
|
||||
"get": {
|
||||
"description": "Get a list of popular movies directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB popular movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/top_rated": {
|
||||
"get": {
|
||||
"description": "Get a list of top rated movies directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB top rated movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/upcoming": {
|
||||
"get": {
|
||||
"description": "Get a list of upcoming movies directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB upcoming movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.TMDBMoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/{id}": {
|
||||
"get": {
|
||||
"description": "Get detailed information about a specific movie directly from TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB movie details",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Movie ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.Movie"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/movie/{id}/external_ids": {
|
||||
"get": {
|
||||
"description": "Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific movie",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB movie external IDs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Movie ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.ExternalIDs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/search/movie": {
|
||||
"get": {
|
||||
"description": "Search for movies directly in TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Search TMDB movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/search/tv": {
|
||||
"get": {
|
||||
"description": "Search for TV shows directly in TMDB",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Search TMDB TV shows",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.TVSearchResults"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/bridge/tmdb/tv/{id}/external_ids": {
|
||||
"get": {
|
||||
"description": "Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific TV show",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"tmdb"
|
||||
],
|
||||
"summary": "Get TMDB TV show external IDs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "TV Show ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tmdb.ExternalIDs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/popular": {
|
||||
"get": {
|
||||
"description": "Get a list of popular movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get popular movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/search": {
|
||||
"get": {
|
||||
"description": "Search for movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Search movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/top-rated": {
|
||||
"get": {
|
||||
"description": "Get a list of top rated movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get top rated movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/upcoming": {
|
||||
"get": {
|
||||
"description": "Get a list of upcoming movies",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get upcoming movies",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number (default: 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MoviesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/movies/{id}": {
|
||||
"get": {
|
||||
"description": "Get detailed information about a specific movie",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"movies"
|
||||
],
|
||||
"summary": "Get movie details",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Movie ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.MovieDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"api.Genre": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.Movie": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Genre"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.MovieDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"budget": {
|
||||
"type": "integer"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Genre"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"revenue": {
|
||||
"type": "integer"
|
||||
},
|
||||
"runtime": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"tagline": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.MoviesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Movie"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"api.TMDBMoviesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/api.Movie"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.ExternalIDs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"facebook_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"imdb_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"instagram_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"twitter_id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.Genre": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.Movie": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tmdb.Genre"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"release_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.MoviesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tmdb.Movie"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.TV": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backdrop_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_air_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"genre_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"original_language": {
|
||||
"type": "string"
|
||||
},
|
||||
"original_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"overview": {
|
||||
"type": "string"
|
||||
},
|
||||
"popularity": {
|
||||
"type": "number"
|
||||
},
|
||||
"poster_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"vote_average": {
|
||||
"type": "number"
|
||||
},
|
||||
"vote_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmdb.TVSearchResults": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"results": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/tmdb.TV"
|
||||
}
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_results": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
basePath: /
|
||||
definitions:
|
||||
api.Genre:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
api.Movie:
|
||||
properties:
|
||||
backdrop_path:
|
||||
type: string
|
||||
genres:
|
||||
items:
|
||||
$ref: '#/definitions/api.Genre'
|
||||
type: array
|
||||
id:
|
||||
type: integer
|
||||
overview:
|
||||
type: string
|
||||
poster_path:
|
||||
type: string
|
||||
release_date:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
vote_average:
|
||||
type: number
|
||||
type: object
|
||||
api.MovieDetails:
|
||||
properties:
|
||||
backdrop_path:
|
||||
type: string
|
||||
budget:
|
||||
type: integer
|
||||
genres:
|
||||
items:
|
||||
$ref: '#/definitions/api.Genre'
|
||||
type: array
|
||||
id:
|
||||
type: integer
|
||||
overview:
|
||||
type: string
|
||||
poster_path:
|
||||
type: string
|
||||
release_date:
|
||||
type: string
|
||||
revenue:
|
||||
type: integer
|
||||
runtime:
|
||||
type: integer
|
||||
status:
|
||||
type: string
|
||||
tagline:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
vote_average:
|
||||
type: number
|
||||
type: object
|
||||
api.MoviesResponse:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
results:
|
||||
items:
|
||||
$ref: '#/definitions/api.Movie'
|
||||
type: array
|
||||
total_pages:
|
||||
type: integer
|
||||
total_results:
|
||||
type: integer
|
||||
type: object
|
||||
api.TMDBMoviesResponse:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
results:
|
||||
items:
|
||||
$ref: '#/definitions/api.Movie'
|
||||
type: array
|
||||
total_pages:
|
||||
type: integer
|
||||
total_results:
|
||||
type: integer
|
||||
type: object
|
||||
tmdb.ExternalIDs:
|
||||
properties:
|
||||
facebook_id:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
imdb_id:
|
||||
type: string
|
||||
instagram_id:
|
||||
type: string
|
||||
twitter_id:
|
||||
type: string
|
||||
type: object
|
||||
tmdb.Genre:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
tmdb.Movie:
|
||||
properties:
|
||||
backdrop_path:
|
||||
type: string
|
||||
genres:
|
||||
items:
|
||||
$ref: '#/definitions/tmdb.Genre'
|
||||
type: array
|
||||
id:
|
||||
type: integer
|
||||
overview:
|
||||
type: string
|
||||
poster_path:
|
||||
type: string
|
||||
release_date:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
vote_average:
|
||||
type: number
|
||||
type: object
|
||||
tmdb.MoviesResponse:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
results:
|
||||
items:
|
||||
$ref: '#/definitions/tmdb.Movie'
|
||||
type: array
|
||||
total_pages:
|
||||
type: integer
|
||||
total_results:
|
||||
type: integer
|
||||
type: object
|
||||
tmdb.TV:
|
||||
properties:
|
||||
backdrop_path:
|
||||
type: string
|
||||
first_air_date:
|
||||
type: string
|
||||
genre_ids:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
original_language:
|
||||
type: string
|
||||
original_name:
|
||||
type: string
|
||||
overview:
|
||||
type: string
|
||||
popularity:
|
||||
type: number
|
||||
poster_path:
|
||||
type: string
|
||||
vote_average:
|
||||
type: number
|
||||
vote_count:
|
||||
type: integer
|
||||
type: object
|
||||
tmdb.TVSearchResults:
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
results:
|
||||
items:
|
||||
$ref: '#/definitions/tmdb.TV'
|
||||
type: array
|
||||
total_pages:
|
||||
type: integer
|
||||
total_results:
|
||||
type: integer
|
||||
type: object
|
||||
host: localhost:8080
|
||||
info:
|
||||
contact: {}
|
||||
description: API для работы с фильмами
|
||||
title: Neo Movies API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/bridge/tmdb/discover/movie:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of movies based on filters
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.TMDBMoviesResponse'
|
||||
summary: Discover movies
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/discover/tv:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of TV shows based on filters
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.TMDBMoviesResponse'
|
||||
summary: Discover TV shows
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/movie/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get detailed information about a specific movie directly from TMDB
|
||||
parameters:
|
||||
- description: Movie ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/tmdb.Movie'
|
||||
summary: Get TMDB movie details
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/movie/{id}/external_ids:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific
|
||||
movie
|
||||
parameters:
|
||||
- description: Movie ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/tmdb.ExternalIDs'
|
||||
summary: Get TMDB movie external IDs
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/movie/popular:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of popular movies directly from TMDB
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.TMDBMoviesResponse'
|
||||
summary: Get TMDB popular movies
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/movie/top_rated:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of top rated movies directly from TMDB
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.TMDBMoviesResponse'
|
||||
summary: Get TMDB top rated movies
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/movie/upcoming:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of upcoming movies directly from TMDB
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.TMDBMoviesResponse'
|
||||
summary: Get TMDB upcoming movies
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/search/movie:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Search for movies directly in TMDB
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/tmdb.MoviesResponse'
|
||||
summary: Search TMDB movies
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/search/tv:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Search for TV shows directly in TMDB
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/tmdb.TVSearchResults'
|
||||
summary: Search TMDB TV shows
|
||||
tags:
|
||||
- tmdb
|
||||
/bridge/tmdb/tv/{id}/external_ids:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific
|
||||
TV show
|
||||
parameters:
|
||||
- description: TV Show ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/tmdb.ExternalIDs'
|
||||
summary: Get TMDB TV show external IDs
|
||||
tags:
|
||||
- tmdb
|
||||
/movies/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get detailed information about a specific movie
|
||||
parameters:
|
||||
- description: Movie ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.MovieDetails'
|
||||
summary: Get movie details
|
||||
tags:
|
||||
- movies
|
||||
/movies/popular:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of popular movies
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.MoviesResponse'
|
||||
summary: Get popular movies
|
||||
tags:
|
||||
- movies
|
||||
/movies/search:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Search for movies
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.MoviesResponse'
|
||||
summary: Search movies
|
||||
tags:
|
||||
- movies
|
||||
/movies/top-rated:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of top rated movies
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.MoviesResponse'
|
||||
summary: Get top rated movies
|
||||
tags:
|
||||
- movies
|
||||
/movies/upcoming:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of upcoming movies
|
||||
parameters:
|
||||
- description: 'Page number (default: 1)'
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/api.MoviesResponse'
|
||||
summary: Get upcoming movies
|
||||
tags:
|
||||
- movies
|
||||
swagger: "2.0"
|
||||
55
go.mod
55
go.mod
@@ -1,55 +0,0 @@
|
||||
module neomovies-api
|
||||
|
||||
go 1.21.0
|
||||
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/bytedance/sonic v1.12.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||
github.com/gin-contrib/cors v1.7.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
h12.io/socks v1.0.3 // indirect
|
||||
)
|
||||
156
go.sum
156
go.sum
@@ -1,156 +0,0 @@
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
|
||||
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
|
||||
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
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.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
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/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
||||
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
|
||||
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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/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=
|
||||
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
|
||||
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
@@ -1,505 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"neomovies-api/internal/tmdb"
|
||||
)
|
||||
|
||||
// GetPopularMovies возвращает список популярных фильмов
|
||||
// @Summary Get popular movies
|
||||
// @Description Get a list of popular movies
|
||||
// @Tags movies
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} MoviesResponse
|
||||
// @Router /movies/popular [get]
|
||||
func GetPopularMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
movies, err := tmdbClient.GetPopular(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
for i := range movies.Results {
|
||||
if movies.Results[i].PosterPath != "" {
|
||||
movies.Results[i].PosterPath = tmdbClient.GetImageURL(movies.Results[i].PosterPath, "w500")
|
||||
}
|
||||
if movies.Results[i].BackdropPath != "" {
|
||||
movies.Results[i].BackdropPath = tmdbClient.GetImageURL(movies.Results[i].BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// GetMovie возвращает информацию о фильме
|
||||
// @Summary Get movie details
|
||||
// @Description Get detailed information about a specific movie
|
||||
// @Tags movies
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Movie ID"
|
||||
// @Success 200 {object} MovieDetails
|
||||
// @Router /movies/{id} [get]
|
||||
func GetMovie(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
movie, err := tmdbClient.GetMovie(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
if movie.PosterPath != "" {
|
||||
movie.PosterPath = tmdbClient.GetImageURL(movie.PosterPath, "original")
|
||||
}
|
||||
if movie.BackdropPath != "" {
|
||||
movie.BackdropPath = tmdbClient.GetImageURL(movie.BackdropPath, "original")
|
||||
}
|
||||
|
||||
// Обрабатываем изображения для коллекции
|
||||
if movie.BelongsToCollection != nil {
|
||||
if movie.BelongsToCollection.PosterPath != "" {
|
||||
movie.BelongsToCollection.PosterPath = tmdbClient.GetImageURL(movie.BelongsToCollection.PosterPath, "w500")
|
||||
}
|
||||
if movie.BelongsToCollection.BackdropPath != "" {
|
||||
movie.BelongsToCollection.BackdropPath = tmdbClient.GetImageURL(movie.BelongsToCollection.BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
// Обрабатываем логотипы компаний
|
||||
for i := range movie.ProductionCompanies {
|
||||
if movie.ProductionCompanies[i].LogoPath != "" {
|
||||
movie.ProductionCompanies[i].LogoPath = tmdbClient.GetImageURL(movie.ProductionCompanies[i].LogoPath, "w185")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movie)
|
||||
}
|
||||
|
||||
// SearchMovies ищет фильмы
|
||||
// @Summary Поиск фильмов
|
||||
// @Description Поиск фильмов по запросу
|
||||
// @Tags movies
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Поисковый запрос"
|
||||
// @Param page query string false "Номер страницы (по умолчанию 1)"
|
||||
// @Success 200 {object} SearchResponse
|
||||
// @Router /movies/search [get]
|
||||
func SearchMovies(c *gin.Context) {
|
||||
query := c.Query("query")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
// Получаем результаты поиска
|
||||
results, err := tmdbClient.SearchMovies(query, page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Преобразуем результаты в формат ответа
|
||||
response := SearchResponse{
|
||||
Page: results.Page,
|
||||
TotalPages: results.TotalPages,
|
||||
TotalResults: results.TotalResults,
|
||||
Results: make([]MovieResponse, 0),
|
||||
}
|
||||
|
||||
// Преобразуем каждый фильм
|
||||
for _, movie := range results.Results {
|
||||
// Форматируем дату
|
||||
releaseDate := formatDate(movie.ReleaseDate)
|
||||
|
||||
// Добавляем фильм в результаты
|
||||
response.Results = append(response.Results, MovieResponse{
|
||||
ID: movie.ID,
|
||||
Title: movie.Title,
|
||||
Overview: movie.Overview,
|
||||
ReleaseDate: releaseDate,
|
||||
VoteAverage: movie.VoteAverage,
|
||||
PosterPath: tmdbClient.GetImageURL(movie.PosterPath, "w500"),
|
||||
BackdropPath: tmdbClient.GetImageURL(movie.BackdropPath, "w1280"),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetTopRatedMovies возвращает список лучших фильмов
|
||||
// @Summary Get top rated movies
|
||||
// @Description Get a list of top rated movies
|
||||
// @Tags movies
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} MoviesResponse
|
||||
// @Router /movies/top-rated [get]
|
||||
func GetTopRatedMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
movies, err := tmdbClient.GetTopRated(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
for i := range movies.Results {
|
||||
if movies.Results[i].PosterPath != "" {
|
||||
movies.Results[i].PosterPath = tmdbClient.GetImageURL(movies.Results[i].PosterPath, "w500")
|
||||
}
|
||||
if movies.Results[i].BackdropPath != "" {
|
||||
movies.Results[i].BackdropPath = tmdbClient.GetImageURL(movies.Results[i].BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// GetUpcomingMovies возвращает список предстоящих фильмов
|
||||
// @Summary Get upcoming movies
|
||||
// @Description Get a list of upcoming movies
|
||||
// @Tags movies
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} MoviesResponse
|
||||
// @Router /movies/upcoming [get]
|
||||
func GetUpcomingMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
movies, err := tmdbClient.GetUpcoming(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
for i := range movies.Results {
|
||||
if movies.Results[i].PosterPath != "" {
|
||||
movies.Results[i].PosterPath = tmdbClient.GetImageURL(movies.Results[i].PosterPath, "w500")
|
||||
}
|
||||
if movies.Results[i].BackdropPath != "" {
|
||||
movies.Results[i].BackdropPath = tmdbClient.GetImageURL(movies.Results[i].BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// GetTMDBPopularMovies возвращает список популярных фильмов из TMDB
|
||||
// @Summary Get TMDB popular movies
|
||||
// @Description Get a list of popular movies directly from TMDB
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} TMDBMoviesResponse
|
||||
// @Router /bridge/tmdb/movie/popular [get]
|
||||
func GetTMDBPopularMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
movies, err := tmdbClient.GetPopular(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
for i := range movies.Results {
|
||||
if movies.Results[i].PosterPath != "" {
|
||||
movies.Results[i].PosterPath = tmdbClient.GetImageURL(movies.Results[i].PosterPath, "w500")
|
||||
}
|
||||
if movies.Results[i].BackdropPath != "" {
|
||||
movies.Results[i].BackdropPath = tmdbClient.GetImageURL(movies.Results[i].BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// GetTMDBMovie возвращает информацию о фильме из TMDB
|
||||
// @Summary Get TMDB movie details
|
||||
// @Description Get detailed information about a specific movie directly from TMDB
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Movie ID"
|
||||
// @Success 200 {object} tmdb.Movie
|
||||
// @Router /bridge/tmdb/movie/{id} [get]
|
||||
func GetTMDBMovie(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
movie, err := tmdbClient.GetMovie(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movie)
|
||||
}
|
||||
|
||||
// GetTMDBTopRatedMovies возвращает список лучших фильмов из TMDB
|
||||
// @Summary Get TMDB top rated movies
|
||||
// @Description Get a list of top rated movies directly from TMDB
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} TMDBMoviesResponse
|
||||
// @Router /bridge/tmdb/movie/top_rated [get]
|
||||
func GetTMDBTopRatedMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
movies, err := tmdbClient.GetTopRated(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
for i := range movies.Results {
|
||||
if movies.Results[i].PosterPath != "" {
|
||||
movies.Results[i].PosterPath = tmdbClient.GetImageURL(movies.Results[i].PosterPath, "w500")
|
||||
}
|
||||
if movies.Results[i].BackdropPath != "" {
|
||||
movies.Results[i].BackdropPath = tmdbClient.GetImageURL(movies.Results[i].BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// GetTMDBUpcomingMovies возвращает список предстоящих фильмов из TMDB
|
||||
// @Summary Get TMDB upcoming movies
|
||||
// @Description Get a list of upcoming movies directly from TMDB
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} TMDBMoviesResponse
|
||||
// @Router /bridge/tmdb/movie/upcoming [get]
|
||||
func GetTMDBUpcomingMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
|
||||
movies, err := tmdbClient.GetUpcoming(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
for i := range movies.Results {
|
||||
if movies.Results[i].PosterPath != "" {
|
||||
movies.Results[i].PosterPath = tmdbClient.GetImageURL(movies.Results[i].PosterPath, "w500")
|
||||
}
|
||||
if movies.Results[i].BackdropPath != "" {
|
||||
movies.Results[i].BackdropPath = tmdbClient.GetImageURL(movies.Results[i].BackdropPath, "w1280")
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// SearchTMDBMovies ищет фильмы в TMDB
|
||||
// @Summary Search TMDB movies
|
||||
// @Description Search for movies directly in TMDB
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Search query"
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} tmdb.MoviesResponse
|
||||
// @Router /bridge/tmdb/search/movie [get]
|
||||
func SearchTMDBMovies(c *gin.Context) {
|
||||
query := c.Query("query")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
page := c.DefaultQuery("page", "1")
|
||||
movies, err := tmdbClient.SearchMovies(query, page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// SearchTMDBTV ищет сериалы в TMDB
|
||||
// @Summary Search TMDB TV shows
|
||||
// @Description Search for TV shows directly in TMDB
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Search query"
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} tmdb.TVSearchResults
|
||||
// @Router /bridge/tmdb/search/tv [get]
|
||||
func SearchTMDBTV(c *gin.Context) {
|
||||
query := c.Query("query")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "query parameter is required"})
|
||||
return
|
||||
}
|
||||
|
||||
page := c.DefaultQuery("page", "1")
|
||||
tv, err := tmdbClient.SearchTV(query, page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, tv)
|
||||
}
|
||||
|
||||
// DiscoverMovies возвращает список фильмов по фильтрам
|
||||
// @Summary Discover movies
|
||||
// @Description Get a list of movies based on filters
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} TMDBMoviesResponse
|
||||
// @Router /bridge/tmdb/discover/movie [get]
|
||||
func DiscoverMovies(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
movies, err := tmdbClient.DiscoverMovies(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, movies)
|
||||
}
|
||||
|
||||
// DiscoverTV возвращает список сериалов по фильтрам
|
||||
// @Summary Discover TV shows
|
||||
// @Description Get a list of TV shows based on filters
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (default: 1)"
|
||||
// @Success 200 {object} TMDBMoviesResponse
|
||||
// @Router /bridge/tmdb/discover/tv [get]
|
||||
func DiscoverTV(c *gin.Context) {
|
||||
page := c.DefaultQuery("page", "1")
|
||||
shows, err := tmdbClient.DiscoverTV(page)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, shows)
|
||||
}
|
||||
|
||||
// GetTMDBMovieExternalIDs возвращает внешние идентификаторы фильма
|
||||
// @Summary Get TMDB movie external IDs
|
||||
// @Description Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific movie
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Movie ID"
|
||||
// @Success 200 {object} tmdb.ExternalIDs
|
||||
// @Router /bridge/tmdb/movie/{id}/external_ids [get]
|
||||
func GetTMDBMovieExternalIDs(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
externalIDs, err := tmdbClient.GetMovieExternalIDs(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, externalIDs)
|
||||
}
|
||||
|
||||
// GetTMDBTVExternalIDs возвращает внешние идентификаторы сериала
|
||||
// @Summary Get TMDB TV show external IDs
|
||||
// @Description Get external IDs (IMDb, Facebook, Instagram, Twitter) for a specific TV show
|
||||
// @Tags tmdb
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "TV Show ID"
|
||||
// @Success 200 {object} tmdb.ExternalIDs
|
||||
// @Router /bridge/tmdb/tv/{id}/external_ids [get]
|
||||
func GetTMDBTVExternalIDs(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
externalIDs, err := tmdbClient.GetTVExternalIDs(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, externalIDs)
|
||||
}
|
||||
|
||||
// HealthCheck godoc
|
||||
// @Summary Проверка работоспособности API
|
||||
// @Description Проверяет, что API работает
|
||||
// @Tags health
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]string
|
||||
// @Router /health [get]
|
||||
func HealthCheck(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
// InitTMDBClientWithProxy инициализирует TMDB клиент с прокси
|
||||
func InitTMDBClientWithProxy(apiKey string, proxyAddr string) error {
|
||||
tmdbClient = tmdb.NewClient(apiKey)
|
||||
return tmdbClient.SetSOCKS5Proxy(proxyAddr)
|
||||
}
|
||||
|
||||
// Admin handlers
|
||||
|
||||
// GetAdminMovies возвращает список фильмов для админа
|
||||
func GetAdminMovies(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Admin movies list"})
|
||||
}
|
||||
|
||||
// ToggleMovieVisibility переключает видимость фильма
|
||||
func ToggleMovieVisibility(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Movie visibility toggled"})
|
||||
}
|
||||
|
||||
// GetUsers возвращает список пользователей
|
||||
func GetUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Users list"})
|
||||
}
|
||||
|
||||
// CreateUser создает нового пользователя
|
||||
func CreateUser(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "User created"})
|
||||
}
|
||||
|
||||
// ToggleAdmin переключает права администратора
|
||||
func ToggleAdmin(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Admin status toggled"})
|
||||
}
|
||||
|
||||
// SendVerification отправляет код верификации
|
||||
func SendVerification(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Verification code sent"})
|
||||
}
|
||||
|
||||
// VerifyCode проверяет код верификации
|
||||
func VerifyCode(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Code verified"})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"neomovies-api/internal/tmdb"
|
||||
)
|
||||
|
||||
var (
|
||||
tmdbClient *tmdb.Client
|
||||
)
|
||||
|
||||
// InitTMDBClient инициализирует TMDB клиент
|
||||
func InitTMDBClient(apiKey string) {
|
||||
tmdbClient = tmdb.NewClient(apiKey)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package api
|
||||
|
||||
// Genre представляет жанр фильма
|
||||
type Genre struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Movie представляет базовую информацию о фильме
|
||||
type Movie struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Overview string `json:"overview"`
|
||||
PosterPath *string `json:"poster_path"`
|
||||
BackdropPath *string `json:"backdrop_path"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
VoteAverage float64 `json:"vote_average"`
|
||||
Genres []Genre `json:"genres"`
|
||||
}
|
||||
|
||||
// MovieDetails представляет детальную информацию о фильме
|
||||
type MovieDetails struct {
|
||||
Movie
|
||||
Runtime int `json:"runtime"`
|
||||
Tagline string `json:"tagline"`
|
||||
Budget int `json:"budget"`
|
||||
Revenue int `json:"revenue"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// MoviesResponse представляет ответ со списком фильмов
|
||||
type MoviesResponse struct {
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
TotalResults int `json:"total_results"`
|
||||
Results []Movie `json:"results"`
|
||||
}
|
||||
|
||||
// TMDBMoviesResponse представляет ответ со списком фильмов от TMDB API
|
||||
type TMDBMoviesResponse struct {
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
TotalResults int `json:"total_results"`
|
||||
Results []Movie `json:"results"`
|
||||
}
|
||||
|
||||
// SearchResponse представляет ответ на поисковый запрос
|
||||
type SearchResponse struct {
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
TotalResults int `json:"total_results"`
|
||||
Results []MovieResponse `json:"results"`
|
||||
}
|
||||
|
||||
// MovieResponse представляет информацию о фильме в ответе API
|
||||
type MovieResponse struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Overview string `json:"overview"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
VoteAverage float64 `json:"vote_average"`
|
||||
PosterPath string `json:"poster_path"`
|
||||
BackdropPath string `json:"backdrop_path"`
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package api
|
||||
|
||||
import "time"
|
||||
|
||||
// formatDate форматирует дату в более читаемый формат
|
||||
func formatDate(date string) string {
|
||||
if date == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Парсим дату из формата YYYY-MM-DD
|
||||
t, err := time.Parse("2006-01-02", date)
|
||||
if err != nil {
|
||||
return date
|
||||
}
|
||||
|
||||
// Форматируем дату в русском стиле
|
||||
months := []string{
|
||||
"января", "февраля", "марта", "апреля", "мая", "июня",
|
||||
"июля", "августа", "сентября", "октября", "ноября", "декабря",
|
||||
}
|
||||
|
||||
return t.Format("2") + " " + months[t.Month()-1] + " " + t.Format("2006")
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
package tmdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://api.themoviedb.org/3"
|
||||
imageBaseURL = "https://image.tmdb.org/t/p"
|
||||
googleDNS = "8.8.8.8:53" // Google Public DNS
|
||||
cloudflareDNS = "1.1.1.1:53" // Cloudflare DNS
|
||||
)
|
||||
|
||||
// Client представляет клиент для работы с TMDB API
|
||||
type Client struct {
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient создает новый клиент TMDB API с кастомным DNS
|
||||
func NewClient(apiKey string) *Client {
|
||||
// Создаем кастомный DNS резолвер с двумя DNS серверами
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
Resolver: &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
// Пробуем сначала Google DNS
|
||||
d := net.Dialer{Timeout: 5 * time.Second}
|
||||
conn, err := d.DialContext(ctx, "udp", googleDNS)
|
||||
if err != nil {
|
||||
log.Printf("Failed to connect to Google DNS, trying Cloudflare: %v", err)
|
||||
// Если Google DNS не отвечает, пробуем Cloudflare
|
||||
return d.DialContext(ctx, "udp", cloudflareDNS)
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Создаем транспорт с кастомным диалером
|
||||
transport := &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
apiKey: apiKey,
|
||||
httpClient: &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
// Проверяем работу DNS и API
|
||||
log.Println("Testing DNS resolution and TMDB API access...")
|
||||
|
||||
// Тест 1: Проверяем резолвинг через DNS
|
||||
ips, err := net.LookupIP("api.themoviedb.org")
|
||||
if err != nil {
|
||||
log.Printf("Warning: DNS lookup failed: %v", err)
|
||||
} else {
|
||||
log.Printf("Successfully resolved api.themoviedb.org to: %v", ips)
|
||||
}
|
||||
|
||||
// Тест 2: Проверяем наш IP
|
||||
resp, err := client.httpClient.Get("https://ipinfo.io/json")
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to check our IP: %v", err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
var ipInfo struct {
|
||||
IP string `json:"ip"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
Org string `json:"org"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&ipInfo); err != nil {
|
||||
log.Printf("Warning: Failed to decode IP info: %v", err)
|
||||
} else {
|
||||
log.Printf("Our IP info: IP=%s, City=%s, Country=%s, Org=%s",
|
||||
ipInfo.IP, ipInfo.City, ipInfo.Country, ipInfo.Org)
|
||||
}
|
||||
}
|
||||
|
||||
// Тест 3: Проверяем доступ к TMDB API
|
||||
testURL := fmt.Sprintf("%s/movie/popular?api_key=%s", baseURL, apiKey)
|
||||
resp, err = client.httpClient.Get(testURL)
|
||||
if err != nil {
|
||||
log.Printf("Warning: TMDB API test failed: %v", err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
log.Println("Successfully connected to TMDB API!")
|
||||
} else {
|
||||
log.Printf("Warning: TMDB API returned status code: %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// SetSOCKS5Proxy устанавливает SOCKS5 прокси для клиента
|
||||
func (c *Client) SetSOCKS5Proxy(proxyAddr string) error {
|
||||
return fmt.Errorf("proxy support has been removed in favor of custom DNS resolvers")
|
||||
}
|
||||
|
||||
// makeRequest выполняет HTTP запрос к TMDB API
|
||||
func (c *Client) makeRequest(method, endpoint string, params url.Values) ([]byte, error) {
|
||||
// Создаем URL
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse base URL: %v", err)
|
||||
}
|
||||
u.Path = path.Join(u.Path, endpoint)
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
// Создаем запрос
|
||||
req, err := http.NewRequest(method, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
// Добавляем заголовок авторизации
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
req.Header.Set("Content-Type", "application/json;charset=utf-8")
|
||||
|
||||
log.Printf("Making request to TMDB: %s %s", method, u.String())
|
||||
|
||||
// Выполняем запрос
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Проверяем статус ответа
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("TMDB API error: status=%d body=%s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// Читаем тело ответа
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %v", err)
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// GetImageURL возвращает полный URL изображения
|
||||
func (c *Client) GetImageURL(path string, size string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s/%s%s", imageBaseURL, size, path)
|
||||
}
|
||||
|
||||
// GetPopular получает список популярных фильмов
|
||||
func (c *Client) GetPopular(page string) (*MoviesResponse, error) {
|
||||
params := url.Values{}
|
||||
params.Set("page", page)
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "movie/popular", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response MoviesResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetMovie получает информацию о конкретном фильме
|
||||
func (c *Client) GetMovie(id string) (*MovieDetails, error) {
|
||||
body, err := c.makeRequest(http.MethodGet, fmt.Sprintf("movie/%s", id), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var movie MovieDetails
|
||||
if err := json.Unmarshal(body, &movie); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &movie, nil
|
||||
}
|
||||
|
||||
// SearchMovies ищет фильмы по запросу с поддержкой русского языка
|
||||
func (c *Client) SearchMovies(query string, page string) (*MoviesResponse, error) {
|
||||
params := url.Values{}
|
||||
params.Set("query", query)
|
||||
params.Set("page", page)
|
||||
params.Set("language", "ru-RU") // Добавляем русский язык
|
||||
params.Set("region", "RU") // Добавляем русский регион
|
||||
params.Set("include_adult", "false") // Исключаем взрослый контент
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "search/movie", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response MoviesResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
// Фильтруем результаты
|
||||
filteredResults := make([]Movie, 0)
|
||||
for _, movie := range response.Results {
|
||||
// Проверяем, что у фильма есть постер и описание
|
||||
if movie.PosterPath != "" && movie.Overview != "" {
|
||||
// Проверяем, что рейтинг больше 0
|
||||
if movie.VoteAverage > 0 {
|
||||
filteredResults = append(filteredResults, movie)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем результаты
|
||||
response.Results = filteredResults
|
||||
response.TotalResults = len(filteredResults)
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetTopRated получает список лучших фильмов
|
||||
func (c *Client) GetTopRated(page string) (*MoviesResponse, error) {
|
||||
params := url.Values{}
|
||||
params.Set("page", page)
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "movie/top_rated", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response MoviesResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// GetUpcoming получает список предстоящих фильмов
|
||||
func (c *Client) GetUpcoming(page string) (*MoviesResponse, error) {
|
||||
params := url.Values{}
|
||||
params.Set("page", page)
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "movie/upcoming", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response MoviesResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// DiscoverMovies получает список фильмов по фильтрам
|
||||
func (c *Client) DiscoverMovies(page string) (*MoviesResponse, error) {
|
||||
params := url.Values{}
|
||||
params.Set("page", page)
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "discover/movie", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response MoviesResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// DiscoverTV получает список сериалов по фильтрам
|
||||
func (c *Client) DiscoverTV(page string) (*MoviesResponse, error) {
|
||||
params := url.Values{}
|
||||
params.Set("page", page)
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "discover/tv", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response MoviesResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// ExternalIDs содержит внешние идентификаторы фильма/сериала
|
||||
type ExternalIDs struct {
|
||||
ID int `json:"id"`
|
||||
IMDbID string `json:"imdb_id"`
|
||||
FacebookID string `json:"facebook_id"`
|
||||
InstagramID string `json:"instagram_id"`
|
||||
TwitterID string `json:"twitter_id"`
|
||||
}
|
||||
|
||||
// GetMovieExternalIDs возвращает внешние идентификаторы фильма
|
||||
func (c *Client) GetMovieExternalIDs(id string) (*ExternalIDs, error) {
|
||||
body, err := c.makeRequest(http.MethodGet, fmt.Sprintf("movie/%s/external_ids", id), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var externalIDs ExternalIDs
|
||||
if err := json.Unmarshal(body, &externalIDs); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &externalIDs, nil
|
||||
}
|
||||
|
||||
// GetTVExternalIDs возвращает внешние идентификаторы сериала
|
||||
func (c *Client) GetTVExternalIDs(id string) (*ExternalIDs, error) {
|
||||
body, err := c.makeRequest(http.MethodGet, fmt.Sprintf("tv/%s/external_ids", id), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var externalIDs ExternalIDs
|
||||
if err := json.Unmarshal(body, &externalIDs); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &externalIDs, nil
|
||||
}
|
||||
|
||||
// TVSearchResults содержит результаты поиска сериалов
|
||||
type TVSearchResults struct {
|
||||
Page int `json:"page"`
|
||||
TotalResults int `json:"total_results"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Results []TV `json:"results"`
|
||||
}
|
||||
|
||||
// TV содержит информацию о сериале
|
||||
type TV struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OriginalName string `json:"original_name"`
|
||||
Overview string `json:"overview"`
|
||||
FirstAirDate string `json:"first_air_date"`
|
||||
PosterPath string `json:"poster_path"`
|
||||
BackdropPath string `json:"backdrop_path"`
|
||||
VoteAverage float64 `json:"vote_average"`
|
||||
VoteCount int `json:"vote_count"`
|
||||
Popularity float64 `json:"popularity"`
|
||||
OriginalLanguage string `json:"original_language"`
|
||||
GenreIDs []int `json:"genre_ids"`
|
||||
}
|
||||
|
||||
// SearchTV ищет сериалы в TMDB
|
||||
func (c *Client) SearchTV(query string, page string) (*TVSearchResults, error) {
|
||||
params := url.Values{}
|
||||
params.Set("query", query)
|
||||
params.Set("page", page)
|
||||
|
||||
body, err := c.makeRequest(http.MethodGet, "search/tv", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results TVSearchResults
|
||||
if err := json.Unmarshal(body, &results); err != nil {
|
||||
return nil, fmt.Errorf("error decoding response: %v", err)
|
||||
}
|
||||
|
||||
return &results, nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package tmdb
|
||||
|
||||
// MoviesResponse представляет ответ от TMDB API со списком фильмов
|
||||
type MoviesResponse struct {
|
||||
Page int `json:"page"`
|
||||
Results []Movie `json:"results"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
TotalResults int `json:"total_results"`
|
||||
}
|
||||
|
||||
// Movie представляет информацию о фильме
|
||||
type Movie struct {
|
||||
Adult bool `json:"adult"`
|
||||
BackdropPath string `json:"backdrop_path"`
|
||||
GenreIDs []int `json:"genre_ids"`
|
||||
ID int `json:"id"`
|
||||
OriginalLanguage string `json:"original_language"`
|
||||
OriginalTitle string `json:"original_title"`
|
||||
Overview string `json:"overview"`
|
||||
Popularity float64 `json:"popularity"`
|
||||
PosterPath string `json:"poster_path"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
Title string `json:"title"`
|
||||
Video bool `json:"video"`
|
||||
VoteAverage float64 `json:"vote_average"`
|
||||
VoteCount int `json:"vote_count"`
|
||||
}
|
||||
|
||||
// Genre представляет жанр фильма
|
||||
type Genre struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Collection представляет коллекцию фильмов
|
||||
type Collection struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PosterPath string `json:"poster_path"`
|
||||
BackdropPath string `json:"backdrop_path"`
|
||||
}
|
||||
|
||||
// ProductionCompany представляет компанию-производителя
|
||||
type ProductionCompany struct {
|
||||
ID int `json:"id"`
|
||||
LogoPath string `json:"logo_path"`
|
||||
Name string `json:"name"`
|
||||
Country string `json:"origin_country"`
|
||||
}
|
||||
|
||||
// MovieDetails представляет детальную информацию о фильме
|
||||
type MovieDetails struct {
|
||||
Adult bool `json:"adult"`
|
||||
BackdropPath string `json:"backdrop_path"`
|
||||
BelongsToCollection *Collection `json:"belongs_to_collection"`
|
||||
Budget int `json:"budget"`
|
||||
Genres []Genre `json:"genres"`
|
||||
Homepage string `json:"homepage"`
|
||||
ID int `json:"id"`
|
||||
IMDbID string `json:"imdb_id"`
|
||||
OriginalLanguage string `json:"original_language"`
|
||||
OriginalTitle string `json:"original_title"`
|
||||
Overview string `json:"overview"`
|
||||
Popularity float64 `json:"popularity"`
|
||||
PosterPath string `json:"poster_path"`
|
||||
ProductionCompanies []ProductionCompany `json:"production_companies"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
Revenue int `json:"revenue"`
|
||||
Runtime int `json:"runtime"`
|
||||
Status string `json:"status"`
|
||||
Tagline string `json:"tagline"`
|
||||
Title string `json:"title"`
|
||||
Video bool `json:"video"`
|
||||
VoteAverage float64 `json:"vote_average"`
|
||||
VoteCount int `json:"vote_count"`
|
||||
}
|
||||
125
main.go
125
main.go
@@ -1,125 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"neomovies-api/internal/api"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
|
||||
_ "neomovies-api/docs"
|
||||
)
|
||||
|
||||
// @title Neo Movies API
|
||||
// @version 1.0
|
||||
// @description API для работы с фильмами
|
||||
// @host localhost:8080
|
||||
// @BasePath /
|
||||
func main() {
|
||||
// Устанавливаем переменные окружения
|
||||
os.Setenv("GIN_MODE", "debug")
|
||||
os.Setenv("PORT", "8080")
|
||||
os.Setenv("TMDB_ACCESS_TOKEN", "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI4ZmU3ODhlYmI5ZDAwNjZiNjQ2MWZhNzk5M2MyMzcxYiIsIm5iZiI6MTcyMzQwMTM3My4yMDgsInN1YiI6IjY2YjkwNDlkNzU4ZDQxOTQwYzA3NjlhNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.x50tvcWDdBTEhtwRb3dE7aEe9qu4sXV_qOjLMn_Vmew")
|
||||
|
||||
// Инициализируем TMDB клиент с CommsOne DNS
|
||||
log.Println("Initializing TMDB client with CommsOne DNS")
|
||||
api.InitTMDBClient(os.Getenv("TMDB_ACCESS_TOKEN"))
|
||||
|
||||
// Устанавливаем режим Gin
|
||||
gin.SetMode(os.Getenv("GIN_MODE"))
|
||||
|
||||
// Создаем роутер
|
||||
r := gin.Default()
|
||||
|
||||
// Настраиваем CORS
|
||||
r.Use(cors.New(cors.Config{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
|
||||
}))
|
||||
|
||||
// Swagger документация
|
||||
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
||||
// Health check
|
||||
r.GET("/health", api.HealthCheck)
|
||||
|
||||
// Movies API
|
||||
movies := r.Group("/movies")
|
||||
{
|
||||
movies.GET("/popular", api.GetPopularMovies)
|
||||
movies.GET("/search", api.SearchMovies)
|
||||
movies.GET("/top-rated", api.GetTopRatedMovies)
|
||||
movies.GET("/upcoming", api.GetUpcomingMovies)
|
||||
movies.GET("/:id", api.GetMovie)
|
||||
}
|
||||
|
||||
// Bridge API
|
||||
bridge := r.Group("/bridge")
|
||||
{
|
||||
// TMDB endpoints
|
||||
tmdb := bridge.Group("/tmdb")
|
||||
{
|
||||
// Movie endpoints
|
||||
movie := tmdb.Group("/movie")
|
||||
{
|
||||
movie.GET("/popular", api.GetTMDBPopularMovies)
|
||||
movie.GET("/top_rated", api.GetTMDBTopRatedMovies)
|
||||
movie.GET("/upcoming", api.GetTMDBUpcomingMovies)
|
||||
movie.GET("/:id", api.GetTMDBMovie)
|
||||
movie.GET("/:id/external_ids", api.GetTMDBMovieExternalIDs)
|
||||
}
|
||||
|
||||
// Search endpoints
|
||||
search := tmdb.Group("/search")
|
||||
{
|
||||
search.GET("/movie", api.SearchTMDBMovies)
|
||||
search.GET("/tv", api.SearchTMDBTV)
|
||||
}
|
||||
|
||||
// TV endpoints
|
||||
tv := tmdb.Group("/tv")
|
||||
{
|
||||
tv.GET("/:id/external_ids", api.GetTMDBTVExternalIDs)
|
||||
}
|
||||
|
||||
// Discover endpoints
|
||||
discover := tmdb.Group("/discover")
|
||||
{
|
||||
discover.GET("/movie", api.DiscoverMovies)
|
||||
discover.GET("/tv", api.DiscoverTV)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Admin API
|
||||
admin := r.Group("/admin")
|
||||
{
|
||||
// Movies endpoints
|
||||
adminMovies := admin.Group("/movies")
|
||||
{
|
||||
adminMovies.GET("", api.GetAdminMovies)
|
||||
adminMovies.POST("/toggle-visibility", api.ToggleMovieVisibility)
|
||||
}
|
||||
|
||||
// Users endpoints
|
||||
adminUsers := admin.Group("/users")
|
||||
{
|
||||
adminUsers.GET("", api.GetUsers)
|
||||
adminUsers.POST("/create", api.CreateUser)
|
||||
adminUsers.POST("/toggle-admin", api.ToggleAdmin)
|
||||
adminUsers.POST("/send-verification", api.SendVerification)
|
||||
adminUsers.POST("/verify-code", api.VerifyCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем сервер
|
||||
port := os.Getenv("PORT")
|
||||
if err := r.Run(":" + port); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
1632
package-lock.json
generated
1632
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "neomovies-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Neo Movies API with TMDB integration",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"build": "npm install --production",
|
||||
"vercel-build": "echo hello",
|
||||
"clean": "rm -rf node_modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
13
render.yaml
13
render.yaml
@@ -1,13 +0,0 @@
|
||||
services:
|
||||
- type: web
|
||||
name: neomovies-api
|
||||
env: go
|
||||
buildCommand: go build -o app
|
||||
startCommand: ./app
|
||||
envVars:
|
||||
- key: GIN_MODE
|
||||
value: release
|
||||
- key: TMDB_ACCESS_TOKEN
|
||||
sync: false
|
||||
healthCheckPath: /health
|
||||
autoDeploy: true
|
||||
7
run.sh
7
run.sh
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Переходим в директорию с приложением
|
||||
cd "$HOME/neomovies-api"
|
||||
|
||||
# Запускаем приложение
|
||||
PORT=$PORT GIN_MODE=release ./app
|
||||
@@ -1,105 +0,0 @@
|
||||
const axios = require('axios');
|
||||
|
||||
class TMDBClient {
|
||||
constructor(accessToken) {
|
||||
this.client = axios.create({
|
||||
baseURL: 'https://api.themoviedb.org/3',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async makeRequest(method, endpoint, params = {}) {
|
||||
try {
|
||||
const response = await this.client.request({
|
||||
method,
|
||||
url: endpoint,
|
||||
params: {
|
||||
...params,
|
||||
language: 'ru-RU',
|
||||
region: 'RU'
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`TMDB API Error: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
getImageURL(path, size = 'original') {
|
||||
if (!path) return null;
|
||||
return `https://image.tmdb.org/t/p/${size}${path}`;
|
||||
}
|
||||
|
||||
async searchMovies(query, page = 1) {
|
||||
const data = await this.makeRequest('GET', '/search/movie', {
|
||||
query,
|
||||
page,
|
||||
include_adult: false
|
||||
});
|
||||
|
||||
// Фильтруем результаты
|
||||
data.results = data.results.filter(movie =>
|
||||
movie.poster_path &&
|
||||
movie.overview &&
|
||||
movie.vote_average > 0
|
||||
);
|
||||
|
||||
// Добавляем полные URL для изображений
|
||||
data.results = data.results.map(movie => ({
|
||||
...movie,
|
||||
poster_path: this.getImageURL(movie.poster_path, 'w500'),
|
||||
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280')
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getMovie(id) {
|
||||
const movie = await this.makeRequest('GET', `/movie/${id}`);
|
||||
return {
|
||||
...movie,
|
||||
poster_path: this.getImageURL(movie.poster_path, 'w500'),
|
||||
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280')
|
||||
};
|
||||
}
|
||||
|
||||
async getPopularMovies(page = 1) {
|
||||
const data = await this.makeRequest('GET', '/movie/popular', { page });
|
||||
data.results = data.results.map(movie => ({
|
||||
...movie,
|
||||
poster_path: this.getImageURL(movie.poster_path, 'w500'),
|
||||
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280')
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
|
||||
async getTopRatedMovies(page = 1) {
|
||||
const data = await this.makeRequest('GET', '/movie/top_rated', { page });
|
||||
data.results = data.results.map(movie => ({
|
||||
...movie,
|
||||
poster_path: this.getImageURL(movie.poster_path, 'w500'),
|
||||
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280')
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
|
||||
async getUpcomingMovies(page = 1) {
|
||||
const data = await this.makeRequest('GET', '/movie/upcoming', { page });
|
||||
data.results = data.results.map(movie => ({
|
||||
...movie,
|
||||
poster_path: this.getImageURL(movie.poster_path, 'w500'),
|
||||
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280')
|
||||
}));
|
||||
return data;
|
||||
}
|
||||
|
||||
async getMovieExternalIDs(id) {
|
||||
return await this.makeRequest('GET', `/movie/${id}/external_ids`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TMDBClient;
|
||||
275
src/index.js
275
src/index.js
@@ -1,275 +0,0 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const swaggerJsdoc = require('swagger-jsdoc');
|
||||
const swaggerUi = require('swagger-ui-express');
|
||||
const path = require('path');
|
||||
const TMDBClient = require('./config/tmdb');
|
||||
const healthCheck = require('./utils/health');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Определяем базовый URL для документации
|
||||
const BASE_URL = process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}`
|
||||
: `http://localhost:${port}`;
|
||||
|
||||
// Swagger configuration
|
||||
const swaggerOptions = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Neo Movies API',
|
||||
version: '1.0.0',
|
||||
description: 'API для поиска и получения информации о фильмах с поддержкой русского языка',
|
||||
contact: {
|
||||
name: 'API Support',
|
||||
url: 'https://gitlab.com/foxixus/neomovies-api'
|
||||
}
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: BASE_URL,
|
||||
description: process.env.VERCEL_URL ? 'Production server' : 'Development server',
|
||||
},
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: 'movies',
|
||||
description: 'Операции с фильмами'
|
||||
},
|
||||
{
|
||||
name: 'health',
|
||||
description: 'Проверка работоспособности API'
|
||||
}
|
||||
],
|
||||
components: {
|
||||
schemas: {
|
||||
Movie: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
description: 'ID фильма'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Название фильма'
|
||||
},
|
||||
overview: {
|
||||
type: 'string',
|
||||
description: 'Описание фильма'
|
||||
},
|
||||
release_date: {
|
||||
type: 'string',
|
||||
format: 'date',
|
||||
description: 'Дата выхода'
|
||||
},
|
||||
vote_average: {
|
||||
type: 'number',
|
||||
description: 'Средняя оценка'
|
||||
},
|
||||
poster_path: {
|
||||
type: 'string',
|
||||
description: 'URL постера'
|
||||
},
|
||||
backdrop_path: {
|
||||
type: 'string',
|
||||
description: 'URL фонового изображения'
|
||||
}
|
||||
}
|
||||
},
|
||||
Error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: {
|
||||
type: 'string',
|
||||
description: 'Сообщение об ошибке'
|
||||
}
|
||||
}
|
||||
},
|
||||
Health: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['healthy', 'unhealthy'],
|
||||
description: 'Общий статус API'
|
||||
},
|
||||
version: {
|
||||
type: 'string',
|
||||
description: 'Версия API'
|
||||
},
|
||||
uptime: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
seconds: {
|
||||
type: 'integer',
|
||||
description: 'Время работы в секундах'
|
||||
},
|
||||
formatted: {
|
||||
type: 'string',
|
||||
description: 'Отформатированное время работы'
|
||||
}
|
||||
}
|
||||
},
|
||||
tmdb: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['ok', 'error'],
|
||||
description: 'Статус подключения к TMDB'
|
||||
},
|
||||
responseTime: {
|
||||
type: 'integer',
|
||||
description: 'Время ответа TMDB в мс'
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
description: 'Сообщение об ошибке, если есть'
|
||||
}
|
||||
}
|
||||
},
|
||||
memory: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
heapTotal: {
|
||||
type: 'integer',
|
||||
description: 'Общий размер кучи (MB)'
|
||||
},
|
||||
heapUsed: {
|
||||
type: 'integer',
|
||||
description: 'Использованный размер кучи (MB)'
|
||||
},
|
||||
rss: {
|
||||
type: 'integer',
|
||||
description: 'Resident Set Size (MB)'
|
||||
},
|
||||
memoryUsage: {
|
||||
type: 'integer',
|
||||
description: 'Процент использования памяти'
|
||||
},
|
||||
system: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
total: {
|
||||
type: 'integer',
|
||||
description: 'Общая память системы (MB)'
|
||||
},
|
||||
free: {
|
||||
type: 'integer',
|
||||
description: 'Свободная память системы (MB)'
|
||||
},
|
||||
usage: {
|
||||
type: 'integer',
|
||||
description: 'Процент использования системной памяти'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
system: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
platform: {
|
||||
type: 'string',
|
||||
description: 'Операционная система'
|
||||
},
|
||||
arch: {
|
||||
type: 'string',
|
||||
description: 'Архитектура процессора'
|
||||
},
|
||||
nodeVersion: {
|
||||
type: 'string',
|
||||
description: 'Версия Node.js'
|
||||
},
|
||||
cpuUsage: {
|
||||
type: 'number',
|
||||
description: 'Загрузка CPU'
|
||||
}
|
||||
}
|
||||
},
|
||||
timestamp: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
description: 'Время проверки'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
apis: ['./src/routes/*.js', './src/index.js'],
|
||||
};
|
||||
|
||||
const swaggerDocs = swaggerJsdoc(swaggerOptions);
|
||||
|
||||
// Custom CSS для Swagger UI
|
||||
const swaggerCustomOptions = {
|
||||
customCss: '.swagger-ui .topbar { display: none }',
|
||||
customSiteTitle: "Neo Movies API Documentation",
|
||||
customfavIcon: "https://www.themoviedb.org/favicon.ico",
|
||||
swaggerOptions: {
|
||||
url: `${BASE_URL}/api-docs/swagger.json`,
|
||||
persistAuthorization: true
|
||||
}
|
||||
};
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// TMDB client middleware
|
||||
app.use((req, res, next) => {
|
||||
if (!process.env.TMDB_ACCESS_TOKEN) {
|
||||
return res.status(500).json({ error: 'TMDB_ACCESS_TOKEN is not set' });
|
||||
}
|
||||
req.tmdb = new TMDBClient(process.env.TMDB_ACCESS_TOKEN);
|
||||
next();
|
||||
});
|
||||
|
||||
// Serve Swagger JSON
|
||||
app.get('/api-docs/swagger.json', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(swaggerDocs);
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.use('/api-docs', swaggerUi.serve);
|
||||
app.get('/api-docs', swaggerUi.setup(null, swaggerCustomOptions));
|
||||
app.use('/movies', require('./routes/movies'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /health:
|
||||
* get:
|
||||
* tags: [health]
|
||||
* summary: Проверка работоспособности API
|
||||
* description: Возвращает подробную информацию о состоянии API, включая статус TMDB, использование памяти и системную информацию
|
||||
* responses:
|
||||
* 200:
|
||||
* description: API работает нормально
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Health'
|
||||
*/
|
||||
app.get('/health', async (req, res) => {
|
||||
const health = await healthCheck.getFullHealth(req.tmdb);
|
||||
res.json(health);
|
||||
});
|
||||
|
||||
// Error handling
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({ error: 'Something went wrong!' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
console.log(`Documentation available at http://localhost:${port}/api-docs`);
|
||||
});
|
||||
@@ -1,325 +0,0 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { formatDate } = require('../utils/date');
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /movies/search:
|
||||
* get:
|
||||
* summary: Поиск фильмов
|
||||
* description: Поиск фильмов по запросу с поддержкой русского языка
|
||||
* tags: [movies]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: query
|
||||
* required: true
|
||||
* description: Поисковый запрос
|
||||
* schema:
|
||||
* type: string
|
||||
* example: Матрица
|
||||
* - in: query
|
||||
* name: page
|
||||
* description: Номер страницы (по умолчанию 1)
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* default: 1
|
||||
* example: 1
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Успешный поиск
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* page:
|
||||
* type: integer
|
||||
* description: Текущая страница
|
||||
* total_pages:
|
||||
* type: integer
|
||||
* description: Всего страниц
|
||||
* total_results:
|
||||
* type: integer
|
||||
* description: Всего результатов
|
||||
* results:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Movie'
|
||||
* 400:
|
||||
* description: Неверный запрос
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/search', async (req, res) => {
|
||||
try {
|
||||
const { query, page = 1 } = req.query;
|
||||
if (!query) {
|
||||
return res.status(400).json({ error: "query parameter is required" });
|
||||
}
|
||||
|
||||
const results = await req.tmdb.searchMovies(query, page);
|
||||
|
||||
const response = {
|
||||
page: results.page,
|
||||
total_pages: results.total_pages,
|
||||
total_results: results.total_results,
|
||||
results: results.results.map(movie => ({
|
||||
id: movie.id,
|
||||
title: movie.title,
|
||||
overview: movie.overview,
|
||||
release_date: formatDate(movie.release_date),
|
||||
vote_average: movie.vote_average,
|
||||
poster_path: movie.poster_path,
|
||||
backdrop_path: movie.backdrop_path
|
||||
}))
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /movies/{id}:
|
||||
* get:
|
||||
* summary: Получить информацию о фильме
|
||||
* description: Получает детальную информацию о фильме по ID
|
||||
* tags: [movies]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* description: ID фильма
|
||||
* schema:
|
||||
* type: integer
|
||||
* example: 603
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Информация о фильме
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Movie'
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const movie = await req.tmdb.getMovie(req.params.id);
|
||||
res.json({
|
||||
...movie,
|
||||
release_date: formatDate(movie.release_date)
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /movies/popular:
|
||||
* get:
|
||||
* summary: Популярные фильмы
|
||||
* description: Получает список популярных фильмов с русскими названиями и описаниями
|
||||
* tags: [movies]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* description: Номер страницы
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* default: 1
|
||||
* example: 1
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Список популярных фильмов
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* page:
|
||||
* type: integer
|
||||
* results:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Movie'
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/popular', async (req, res) => {
|
||||
try {
|
||||
const { page = 1 } = req.query;
|
||||
const movies = await req.tmdb.getPopularMovies(page);
|
||||
res.json(movies);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /movies/top-rated:
|
||||
* get:
|
||||
* summary: Лучшие фильмы
|
||||
* description: Получает список лучших фильмов с русскими названиями и описаниями
|
||||
* tags: [movies]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* description: Номер страницы
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* default: 1
|
||||
* example: 1
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Список лучших фильмов
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* page:
|
||||
* type: integer
|
||||
* results:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Movie'
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/top-rated', async (req, res) => {
|
||||
try {
|
||||
const { page = 1 } = req.query;
|
||||
const movies = await req.tmdb.getTopRatedMovies(page);
|
||||
res.json(movies);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /movies/upcoming:
|
||||
* get:
|
||||
* summary: Предстоящие фильмы
|
||||
* description: Получает список предстоящих фильмов с русскими названиями и описаниями
|
||||
* tags: [movies]
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* description: Номер страницы
|
||||
* schema:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* default: 1
|
||||
* example: 1
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Список предстоящих фильмов
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* page:
|
||||
* type: integer
|
||||
* results:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Movie'
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/upcoming', async (req, res) => {
|
||||
try {
|
||||
const { page = 1 } = req.query;
|
||||
const movies = await req.tmdb.getUpcomingMovies(page);
|
||||
res.json(movies);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /movies/{id}/external-ids:
|
||||
* get:
|
||||
* summary: Внешние ID фильма
|
||||
* description: Получает внешние идентификаторы фильма (IMDb и др.)
|
||||
* tags: [movies]
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* description: ID фильма
|
||||
* schema:
|
||||
* type: integer
|
||||
* example: 603
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Внешние ID фильма
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* imdb_id:
|
||||
* type: string
|
||||
* description: ID на IMDb
|
||||
* facebook_id:
|
||||
* type: string
|
||||
* description: ID на Facebook
|
||||
* instagram_id:
|
||||
* type: string
|
||||
* description: ID на Instagram
|
||||
* twitter_id:
|
||||
* type: string
|
||||
* description: ID на Twitter
|
||||
* 500:
|
||||
* description: Ошибка сервера
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
router.get('/:id/external-ids', async (req, res) => {
|
||||
try {
|
||||
const externalIds = await req.tmdb.getMovieExternalIDs(req.params.id);
|
||||
res.json(externalIds);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,13 +0,0 @@
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return null;
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatDate
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
const os = require('os');
|
||||
const process = require('process');
|
||||
|
||||
class HealthCheck {
|
||||
constructor() {
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
|
||||
getUptime() {
|
||||
return Math.floor((Date.now() - this.startTime) / 1000);
|
||||
}
|
||||
|
||||
getMemoryUsage() {
|
||||
const used = process.memoryUsage();
|
||||
return {
|
||||
heapTotal: Math.round(used.heapTotal / 1024 / 1024), // MB
|
||||
heapUsed: Math.round(used.heapUsed / 1024 / 1024), // MB
|
||||
rss: Math.round(used.rss / 1024 / 1024), // MB
|
||||
memoryUsage: Math.round((used.heapUsed / used.heapTotal) * 100) // %
|
||||
};
|
||||
}
|
||||
|
||||
getSystemInfo() {
|
||||
return {
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
nodeVersion: process.version,
|
||||
cpuUsage: Math.round(os.loadavg()[0] * 100) / 100,
|
||||
totalMemory: Math.round(os.totalmem() / 1024 / 1024), // MB
|
||||
freeMemory: Math.round(os.freemem() / 1024 / 1024) // MB
|
||||
};
|
||||
}
|
||||
|
||||
async checkTMDBConnection(tmdbClient) {
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
await tmdbClient.makeRequest('GET', '/configuration');
|
||||
const endTime = Date.now();
|
||||
return {
|
||||
status: 'ok',
|
||||
responseTime: endTime - startTime
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'error',
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
formatUptime(seconds) {
|
||||
const days = Math.floor(seconds / (24 * 60 * 60));
|
||||
const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60));
|
||||
const minutes = Math.floor((seconds % (60 * 60)) / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
|
||||
const parts = [];
|
||||
if (days > 0) parts.push(`${days}d`);
|
||||
if (hours > 0) parts.push(`${hours}h`);
|
||||
if (minutes > 0) parts.push(`${minutes}m`);
|
||||
if (remainingSeconds > 0 || parts.length === 0) parts.push(`${remainingSeconds}s`);
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
async getFullHealth(tmdbClient) {
|
||||
const uptime = this.getUptime();
|
||||
const tmdbStatus = await this.checkTMDBConnection(tmdbClient);
|
||||
const memory = this.getMemoryUsage();
|
||||
const system = this.getSystemInfo();
|
||||
|
||||
return {
|
||||
status: tmdbStatus.status === 'ok' ? 'healthy' : 'unhealthy',
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
uptime: {
|
||||
seconds: uptime,
|
||||
formatted: this.formatUptime(uptime)
|
||||
},
|
||||
tmdb: {
|
||||
status: tmdbStatus.status,
|
||||
responseTime: tmdbStatus.responseTime,
|
||||
error: tmdbStatus.error
|
||||
},
|
||||
memory: {
|
||||
...memory,
|
||||
system: {
|
||||
total: system.totalMemory,
|
||||
free: system.freeMemory,
|
||||
usage: Math.round(((system.totalMemory - system.freeMemory) / system.totalMemory) * 100)
|
||||
}
|
||||
},
|
||||
system: {
|
||||
platform: system.platform,
|
||||
arch: system.arch,
|
||||
nodeVersion: system.nodeVersion,
|
||||
cpuUsage: system.cpuUsage
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new HealthCheck();
|
||||
22
vercel.json
22
vercel.json
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{
|
||||
"src": "src/index.js",
|
||||
"use": "@vercel/node"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"src": "/api-docs/(.*)",
|
||||
"dest": "src/index.js"
|
||||
},
|
||||
{
|
||||
"src": "/(.*)",
|
||||
"dest": "src/index.js"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user