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:
2025-01-03 19:36:22 +00:00
parent e4bfcebcf4
commit 2393d88add
25 changed files with 0 additions and 6164 deletions

View File

@@ -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_tmdb_access_token
```
## Запуск
Для разработки:
```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

View File

@@ -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"

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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"
}
}
}
}
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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"})
}

View File

@@ -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)
}

View File

@@ -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"`
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -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
View File

@@ -1,7 +0,0 @@
#!/bin/bash
# Переходим в директорию с приложением
cd "$HOME/neomovies-api"
# Запускаем приложение
PORT=$PORT GIN_MODE=release ./app

View File

@@ -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;

View File

@@ -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`);
});

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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();

View File

@@ -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"
}
}