Update 16 files

- /docs/swagger.yaml
- /docs/swagger.json
- /docs/docs.go
- /internal/api/init.go
- /internal/api/models.go
- /internal/api/handlers.go
- /internal/api/utils.go
- /internal/tmdb/models.go
- /internal/tmdb/client.go
- /build.sh
- /go.mod
- /go.sum
- /main.go
- /render.yaml
- /run.sh
- /README.md
This commit is contained in:
2025-01-03 18:42:44 +00:00
parent 46735b80e8
commit 612e49817c
16 changed files with 3618 additions and 79 deletions

152
README.md
View File

@@ -1,93 +1,87 @@
# neomovies-api
# Neo Movies API
API для поиска фильмов и сериалов с поддержкой русского языка.
## Деплой на AlwaysData
## Getting started
1. Создайте аккаунт на [AlwaysData](https://www.alwaysdata.com)
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
2. Настройте SSH ключ:
```bash
# Создайте SSH ключ если его нет
ssh-keygen -t rsa -b 4096
# Скопируйте публичный ключ
cat ~/.ssh/id_rsa.pub
```
Добавьте ключ в настройках AlwaysData (SSH Keys)
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
3. Подключитесь по SSH:
```bash
# Замените username на ваш логин
ssh username@ssh-username.alwaysdata.net
```
## Add your files
4. Установите Go:
```bash
# Создайте директорию для Go
mkdir -p $HOME/go/bin
# Скачайте и установите Go
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
tar -C $HOME -xzf go1.21.5.linux-amd64.tar.gz
# Добавьте Go в PATH
echo 'export PATH=$HOME/go/bin:$HOME/go/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
```
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
5. Клонируйте репозиторий:
```bash
git clone https://github.com/ваш-username/neomovies-api.git
cd neomovies-api
```
```
cd existing_repo
git remote add origin https://gitlab.com/foxixus/neomovies-api.git
git branch -M main
git push -uf origin main
6. Соберите приложение:
```bash
chmod +x build.sh
./build.sh
```
7. Настройте сервис в панели AlwaysData:
- Type: Site
- Name: neomovies-api
- Address: api.your-name.alwaysdata.net
- Command: $HOME/neomovies-api/run.sh
- Working directory: $HOME/neomovies-api
8. Добавьте переменные окружения:
- `TMDB_ACCESS_TOKEN`: Ваш токен TMDB API
- `PORT`: 8080 (или порт по умолчанию)
После деплоя ваше API будет доступно по адресу: https://api.your-name.alwaysdata.net
## Локальная разработка
1. Установите зависимости:
```bash
go mod download
```
## Integrate with your tools
2. Запустите сервер:
```bash
go run main.go
```
- [ ] [Set up project integrations](https://gitlab.com/foxixus/neomovies-api/-/settings/integrations)
API будет доступно по адресу: http://localhost:8080
## Collaborate with your team
## API Endpoints
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
- `GET /movies/search` - Поиск фильмов
- `GET /movies/popular` - Популярные фильмы
- `GET /movies/top-rated` - Лучшие фильмы
- `GET /movies/upcoming` - Предстоящие фильмы
- `GET /movies/:id` - Информация о фильме
- `GET /health` - Проверка работоспособности API
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Полная документация API доступна по адресу: `/swagger/index.html`

7
build.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Переходим в директорию с приложением
cd "$HOME/neomovies-api"
# Собираем приложение
go build -o app

806
docs/docs.go Normal file
View File

@@ -0,0 +1,806 @@
// 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)
}

782
docs/swagger.json Normal file
View File

@@ -0,0 +1,782 @@
{
"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"
}
}
}
}
}

512
docs/swagger.yaml Normal file
View File

@@ -0,0 +1,512 @@
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 Normal file
View File

@@ -0,0 +1,55 @@
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 Normal file
View File

@@ -0,0 +1,156 @@
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=

505
internal/api/handlers.go Normal file
View File

@@ -0,0 +1,505 @@
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"})
}

14
internal/api/init.go Normal file
View File

@@ -0,0 +1,14 @@
package api
import (
"neomovies-api/internal/tmdb"
)
var (
tmdbClient *tmdb.Client
)
// InitTMDBClient инициализирует TMDB клиент
func InitTMDBClient(apiKey string) {
tmdbClient = tmdb.NewClient(apiKey)
}

64
internal/api/models.go Normal file
View File

@@ -0,0 +1,64 @@
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"`
}

24
internal/api/utils.go Normal file
View File

@@ -0,0 +1,24 @@
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")
}

399
internal/tmdb/client.go Normal file
View File

@@ -0,0 +1,399 @@
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
}

76
internal/tmdb/models.go Normal file
View File

@@ -0,0 +1,76 @@
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 Normal file
View File

@@ -0,0 +1,125 @@
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)
}
}

13
render.yaml Normal file
View File

@@ -0,0 +1,13 @@
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 Normal file
View File

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