Compare commits
40 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b40ebdbd9 | |||
| 522e7554fe | |||
| 90be4d134b | |||
| 82842b3bf3 | |||
| b33997772b | |||
| 490443b63f | |||
| 9affe90988 | |||
| 8056946f03 | |||
| 541d0fce27 | |||
| 4102aa20d2 | |||
| 103a872be2 | |||
| dfd3bb5f48 | |||
| e62f0fa96c | |||
| 53011b67ae | |||
| 6f808a715b | |||
| b239a66dbf | |||
| 7787eb328f | |||
| eef3696e5e | |||
| fe1bf7ec10 | |||
|
|
f983ed1035 | ||
|
|
9b2e9bd45a | ||
| 1b6c536b77 | |||
|
|
3d0ffd0df3 | ||
| 486c6d8407 | |||
| 5fb7b16c96 | |||
| da9d0f8dda | |||
| 69eacd7240 | |||
| 714ef57027 | |||
|
|
74d6adf23a | ||
|
|
1012ac22b6 | ||
|
|
fc0ddf334d | ||
| 713c0adc14 | |||
|
|
93f12666cd | ||
|
|
aa0d837c68 | ||
|
|
ff36173720 | ||
|
|
0f619dd954 | ||
|
|
b6578cfb0c | ||
|
|
9c59fb1328 | ||
|
|
e11c5aec00 | ||
|
|
df188cb531 |
140 changed files with 5467 additions and 6288 deletions
|
|
@ -100,6 +100,23 @@ jobs:
|
||||||
push: true
|
push: true
|
||||||
tags: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
tags: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
||||||
|
|
||||||
|
- name: Build and push etl image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfiles/Dockerfile_etl
|
||||||
|
push: true
|
||||||
|
tags: meowgit.nekoea.red/nihonium/nyanimedb-etl:latest
|
||||||
|
|
||||||
|
- name: Build and push image-storage image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfiles/Dockerfile_image_storage
|
||||||
|
push: true
|
||||||
|
tags: meowgit.nekoea.red/nihonium/nyanimedb-image-storage:latest
|
||||||
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: debian-test
|
runs-on: debian-test
|
||||||
needs: build
|
needs: build
|
||||||
|
|
|
||||||
49
.forgejo/workflows/dev-ars.yml
Normal file
49
.forgejo/workflows/dev-ars.yml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
name: Build (backend build only)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev-ars
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Build backend
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: '^1.25'
|
||||||
|
|
||||||
|
- name: Build backend
|
||||||
|
run: |
|
||||||
|
cd modules/backend
|
||||||
|
go build -o nyanimedb .
|
||||||
|
tar -czvf nyanimedb-backend.tar.gz nyanimedb
|
||||||
|
|
||||||
|
- name: Upload built backend to artifactory
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: nyanimedb-backend.tar.gz
|
||||||
|
path: modules/backend/nyanimedb-backend.tar.gz
|
||||||
|
|
||||||
|
# Build Docker images
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ vars.REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push backend image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfiles/Dockerfile_backend
|
||||||
|
push: true
|
||||||
|
tags: meowgit.nekoea.red/nihonium/nyanimedb-backend:latest
|
||||||
54
.forgejo/workflows/front.yml
Normal file
54
.forgejo/workflows/front.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: Build (frontend build only)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- front
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version-file: modules/frontend/package.json
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: modules/frontend/package-lock.json
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
env:
|
||||||
|
VITE_BACKEND_API_BASE_URL: ${{ vars.BACKEND_API_BASE_URL }}
|
||||||
|
run: |
|
||||||
|
cd modules/frontend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
tar -czvf nyanimedb-frontend.tar.gz dist/
|
||||||
|
|
||||||
|
- name: Upload built frontend to artifactory
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: nyanimedb-frontend.tar.gz
|
||||||
|
path: modules/frontend/nyanimedb-frontend.tar.gz
|
||||||
|
|
||||||
|
# Build Docker images
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ vars.REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push frontend image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfiles/Dockerfile_frontend
|
||||||
|
push: true
|
||||||
|
tags: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
||||||
13
Dockerfiles/Dockerfile_etl
Normal file
13
Dockerfiles/Dockerfile_etl
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app/modules/anime_etl
|
||||||
|
COPY modules/anime_etl/pyproject.toml modules/anime_etl/uv.lock ./
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir uv \
|
||||||
|
&& uv sync --frozen --no-dev
|
||||||
|
|
||||||
|
COPY modules/anime_etl ./
|
||||||
|
|
||||||
|
ENV NYANIMEDB_MEDIA_ROOT=/media
|
||||||
|
|
||||||
|
CMD ["uv", "run", "python", "-m", "rabbit_worker"]
|
||||||
15
Dockerfiles/Dockerfile_image_storage
Normal file
15
Dockerfiles/Dockerfile_image_storage
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app/modules/image_storage
|
||||||
|
COPY modules/image_storage/pyproject.toml modules/image_storage/uv.lock ./
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir uv \
|
||||||
|
&& uv sync --frozen --no-dev
|
||||||
|
|
||||||
|
COPY modules/image_storage ./
|
||||||
|
|
||||||
|
ENV NYANIMEDB_MEDIA_ROOT=/media
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
1
api/.gitignore
vendored
1
api/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
generated-client/
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
# C++ API client
|
|
||||||
|
|
||||||
{{#appDescriptionWithNewLines}}
|
|
||||||
{{{.}}}
|
|
||||||
{{/appDescriptionWithNewLines}}
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [OpenAPI spec](https://openapis.org) from a remote server, you can easily generate an API client.
|
|
||||||
|
|
||||||
- API version: {{appVersion}}
|
|
||||||
- Package version: {{packageVersion}}
|
|
||||||
{{^hideGenerationTimestamp}}
|
|
||||||
- Build date: {{generatedDate}}
|
|
||||||
{{/hideGenerationTimestamp}}
|
|
||||||
- Generator version: {{generatorVersion}}
|
|
||||||
- Build package: {{generatorClass}}
|
|
||||||
{{#infoUrl}}
|
|
||||||
For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
|
|
||||||
{{/infoUrl}}
|
|
||||||
|
|
||||||
- API namespace: {{{apiPackage}}}
|
|
||||||
- Model namespace: {{{modelPackage}}}
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
Install [cpprestsdk](https://github.com/Microsoft/cpprestsdk).
|
|
||||||
|
|
||||||
- Windows: `vcpkg install cpprestsdk cpprestsdk:x64-windows boost-uuid boost-uuid:x64-windows`
|
|
||||||
- Mac: `brew install cpprestsdk`
|
|
||||||
- Linux: `sudo apt-get install libcpprest-dev`
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cmake -DCPPREST_ROOT=/usr -DCMAKE_CXX_FLAGS="-I/usr/local/opt/openssl/include" -DCMAKE_MODULE_LINKER_FLAGS="-L/usr/local/opt/openssl/lib"
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build on Windows with Visual Studio (VS2017)
|
|
||||||
|
|
||||||
- Right click on folder containing source code
|
|
||||||
- Select 'Open in visual studio'
|
|
||||||
- Once visual studio opens, CMake should show up in top menu bar.
|
|
||||||
- Select CMake > Build All.
|
|
||||||
|
|
||||||
*Note: If the CMake menu item doesn't show up in Visual Studio, CMake
|
|
||||||
for Visual Studio must be installed. In this case, open the 'Visual Studio
|
|
||||||
Installer' application. Select 'modify' Visual Studio 2017. Make sure
|
|
||||||
'Desktop Development with C++' is installed, and specifically that 'Visual
|
|
||||||
C++ tools for CMake' is selected in the 'Installation Details' section.
|
|
||||||
|
|
||||||
Also be sure to review the CMakeLists.txt file. Edits are likely required.*
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}}
|
|
||||||
{{/-last}}{{/apis}}{{/apiInfo}}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* AnyType.h
|
|
||||||
*
|
|
||||||
* This is the implementation of an any JSON type.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_AnyType_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_AnyType_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include "{{packageName}}/Object.h"
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
#include <cpprest/json.h>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} AnyType : public Object {
|
|
||||||
public:
|
|
||||||
AnyType();
|
|
||||||
virtual ~AnyType();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// ModelBase overrides
|
|
||||||
void validate() override;
|
|
||||||
|
|
||||||
web::json::value toJson() const override;
|
|
||||||
bool fromJson(const web::json::value &json) override;
|
|
||||||
|
|
||||||
void toMultipart(std::shared_ptr<MultipartFormData> multipart,
|
|
||||||
const utility::string_t &namePrefix) const override;
|
|
||||||
bool fromMultiPart(std::shared_ptr<MultipartFormData> multipart,
|
|
||||||
const utility::string_t &namePrefix) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
web::json::value m_value;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_AnyType_H_ */
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/AnyType.h"
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
AnyType::AnyType() { m_value = web::json::value::null(); }
|
|
||||||
|
|
||||||
AnyType::~AnyType() {}
|
|
||||||
|
|
||||||
void AnyType::validate() {}
|
|
||||||
|
|
||||||
web::json::value AnyType::toJson() const { return m_value; }
|
|
||||||
|
|
||||||
bool AnyType::fromJson(const web::json::value &val) {
|
|
||||||
m_value = val;
|
|
||||||
m_IsSet = true;
|
|
||||||
return isSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnyType::toMultipart(std::shared_ptr<MultipartFormData> multipart,
|
|
||||||
const utility::string_t &prefix) const {
|
|
||||||
if (m_value.is_object()) {
|
|
||||||
return Object::toMultipart(multipart, prefix);
|
|
||||||
}
|
|
||||||
throw std::runtime_error("AnyType::toMultipart: unsupported type");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AnyType::fromMultiPart(std::shared_ptr<MultipartFormData> multipart,
|
|
||||||
const utility::string_t &prefix) {
|
|
||||||
if (m_value.is_object()) {
|
|
||||||
return Object::fromMultiPart(multipart, prefix);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#operations}}
|
|
||||||
#ifndef {{apiHeaderGuardPrefix}}_{{classname}}GMock_H_
|
|
||||||
#define {{apiHeaderGuardPrefix}}_{{classname}}GMock_H_
|
|
||||||
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
|
|
||||||
#include "{{classname}}.h"
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
|
|
||||||
class {{declspec}} {{classname}}Mock : public I{{classname}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using Base = I{{classname}};
|
|
||||||
|
|
||||||
{{classname}}Mock() = default;
|
|
||||||
explicit {{classname}}Mock( std::shared_ptr<ApiClient> apiClient ) { };
|
|
||||||
~{{classname}}Mock() override = default;
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
MOCK_METHOD{{allParams.size}}( {{operationId}}, pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> (
|
|
||||||
{{#allParams}}
|
|
||||||
{{^required}}boost::optional<{{/required}}{{#isFile}}std::shared_ptr<{{/isFile}}{{{dataType}}}{{#isFile}}>{{/isFile}}{{^required}}>{{/required}} {{paramName}}{{^-last}},{{/-last}}
|
|
||||||
{{/allParams}}
|
|
||||||
) );
|
|
||||||
{{/operation}}
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{apiHeaderGuardPrefix}}_{{classname}}GMock_H_ */
|
|
||||||
|
|
||||||
{{/operations}}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#operations}}/*
|
|
||||||
* {{classname}}.h
|
|
||||||
*
|
|
||||||
* {{description}}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{apiHeaderGuardPrefix}}_{{classname}}_H_
|
|
||||||
#define {{apiHeaderGuardPrefix}}_{{classname}}_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
|
|
||||||
#include "{{packageName}}/ApiClient.h"
|
|
||||||
{{^hasModelImport}}#include "{{packageName}}/ModelBase.h"{{/hasModelImport}}
|
|
||||||
{{#imports}}{{{import}}}
|
|
||||||
{{/imports}}
|
|
||||||
#include <boost/optional.hpp>
|
|
||||||
#include <map> // <-- добавлено для std::map
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
{{#gmockApis}}
|
|
||||||
class {{declspec}} I{{classname}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
I{{classname}}() = default;
|
|
||||||
virtual ~I{{classname}}() = default;
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
virtual pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> {{operationId}}(
|
|
||||||
{{#allParams}}
|
|
||||||
{{^required}}boost::optional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}},{{/allParams}}
|
|
||||||
const std::map<utility::string_t, utility::string_t>& customHeaders = {} // <-- добавлено
|
|
||||||
) const = 0;
|
|
||||||
{{/operation}}
|
|
||||||
};{{/gmockApis}}
|
|
||||||
|
|
||||||
class {{declspec}} {{classname}} {{#gmockApis}} : public I{{classname}} {{/gmockApis}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
{{#gmockApis}}
|
|
||||||
using Base = I{{classname}};
|
|
||||||
{{/gmockApis}}
|
|
||||||
|
|
||||||
explicit {{classname}}( std::shared_ptr<const ApiClient> apiClient );
|
|
||||||
|
|
||||||
{{#gmockApis}}
|
|
||||||
~{{classname}}() override;
|
|
||||||
{{/gmockApis}}
|
|
||||||
{{^gmockApis}}
|
|
||||||
virtual ~{{classname}}();
|
|
||||||
{{/gmockApis}}
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
/// <summary>
|
|
||||||
/// {{summary}}
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// {{notes}}
|
|
||||||
/// </remarks>
|
|
||||||
{{#allParams}}
|
|
||||||
/// <param name="{{paramName}}">{{#lambda.multiline_comment_4}}{{description}}{{/lambda.multiline_comment_4}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}</param>
|
|
||||||
{{/allParams}}
|
|
||||||
/// <param name="customHeaders">Additional HTTP headers to send with the request (optional)</param>
|
|
||||||
pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> {{operationId}}(
|
|
||||||
{{#allParams}}
|
|
||||||
{{^required}}boost::optional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}},{{/allParams}}
|
|
||||||
const std::map<utility::string_t, utility::string_t>& customHeaders = {} // <-- добавлено
|
|
||||||
) const{{#gmockApis}} override{{/gmockApis}};
|
|
||||||
{{/operation}}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::shared_ptr<const ApiClient> m_ApiClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{apiHeaderGuardPrefix}}_{{classname}}_H_ */
|
|
||||||
|
|
||||||
{{/operations}}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#operations}}/*
|
|
||||||
* {{classname}}.h
|
|
||||||
*
|
|
||||||
* {{description}}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{apiHeaderGuardPrefix}}_{{classname}}_H_
|
|
||||||
#define {{apiHeaderGuardPrefix}}_{{classname}}_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
|
|
||||||
#include "{{packageName}}/ApiClient.h"
|
|
||||||
{{^hasModelImport}}#include "{{packageName}}/ModelBase.h"{{/hasModelImport}}
|
|
||||||
{{#imports}}{{{import}}}
|
|
||||||
{{/imports}}
|
|
||||||
#include <boost/optional.hpp>
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
{{#gmockApis}}
|
|
||||||
class {{declspec}} I{{classname}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
I{{classname}}() = default;
|
|
||||||
virtual ~I{{classname}}() = default;
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
virtual pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> {{operationId}}(
|
|
||||||
{{#allParams}}
|
|
||||||
{{^required}}boost::optional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}}{{^-last}},{{/-last}}
|
|
||||||
{{/allParams}}
|
|
||||||
) const = 0;
|
|
||||||
{{/operation}}
|
|
||||||
};{{/gmockApis}}
|
|
||||||
|
|
||||||
class {{declspec}} {{classname}} {{#gmockApis}} : public I{{classname}} {{/gmockApis}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
{{#gmockApis}}
|
|
||||||
using Base = I{{classname}};
|
|
||||||
{{/gmockApis}}
|
|
||||||
|
|
||||||
explicit {{classname}}( std::shared_ptr<const ApiClient> apiClient );
|
|
||||||
|
|
||||||
{{#gmockApis}}
|
|
||||||
~{{classname}}() override;
|
|
||||||
{{/gmockApis}}
|
|
||||||
{{^gmockApis}}
|
|
||||||
virtual ~{{classname}}();
|
|
||||||
{{/gmockApis}}
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
/// <summary>
|
|
||||||
/// {{summary}}
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// {{notes}}
|
|
||||||
/// </remarks>
|
|
||||||
{{#allParams}}
|
|
||||||
/// <param name="{{paramName}}">{{#lambda.multiline_comment_4}}{{description}}{{/lambda.multiline_comment_4}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}</param>
|
|
||||||
{{/allParams}}
|
|
||||||
pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> {{operationId}}(
|
|
||||||
{{#allParams}}
|
|
||||||
{{^required}}boost::optional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}}{{^-last}},{{/-last}}
|
|
||||||
{{/allParams}}
|
|
||||||
) const{{#gmockApis}} override{{/gmockApis}};
|
|
||||||
{{/operation}}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::shared_ptr<const ApiClient> m_ApiClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{apiHeaderGuardPrefix}}_{{classname}}_H_ */
|
|
||||||
|
|
||||||
{{/operations}}
|
|
||||||
|
|
@ -1,375 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#operations}}
|
|
||||||
|
|
||||||
#include "{{packageName}}/api/{{classname}}.h"
|
|
||||||
#include "{{packageName}}/IHttpBody.h"
|
|
||||||
#include "{{packageName}}/JsonBody.h"
|
|
||||||
#include "{{packageName}}/MultipartFormData.h"
|
|
||||||
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
|
||||||
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
{{classname}}::{{classname}}( std::shared_ptr<const ApiClient> apiClient )
|
|
||||||
: m_ApiClient(apiClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::~{{classname}}()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> {{classname}}::{{operationId}}({{#allParams}}{{^required}}boost::optional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}}, {{/allParams}}const std::map<utility::string_t, utility::string_t>& customHeaders) const // <-- изменено: все параметры с запятой, + customHeaders
|
|
||||||
{
|
|
||||||
{{#allParams}}{{#required}}{{^isPrimitiveType}}{{^isContainer}}
|
|
||||||
// verify the required parameter '{{paramName}}' is set
|
|
||||||
if ({{paramName}} == nullptr)
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}"));
|
|
||||||
}
|
|
||||||
{{/isContainer}}{{/isPrimitiveType}}{{/required}}{{/allParams}}
|
|
||||||
|
|
||||||
std::shared_ptr<const ApiConfiguration> localVarApiConfiguration( m_ApiClient->getConfiguration() );
|
|
||||||
utility::string_t localVarPath = utility::conversions::to_string_t("{{{path}}}");
|
|
||||||
{{#pathParams}}
|
|
||||||
boost::replace_all(localVarPath, utility::conversions::to_string_t("{") + utility::conversions::to_string_t("{{baseName}}") + utility::conversions::to_string_t("}"), web::uri::encode_uri(ApiClient::parameterToString({{{paramName}}})));
|
|
||||||
{{/pathParams}}
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t> localVarQueryParams;
|
|
||||||
std::map<utility::string_t, utility::string_t> localVarHeaderParams( localVarApiConfiguration->getDefaultHeaders() ); // <-- уже содержит defaultHeaders
|
|
||||||
std::map<utility::string_t, utility::string_t> localVarFormParams;
|
|
||||||
std::map<utility::string_t, std::shared_ptr<HttpContent>> localVarFileParams;
|
|
||||||
|
|
||||||
std::unordered_set<utility::string_t> localVarResponseHttpContentTypes;
|
|
||||||
{{#produces}}
|
|
||||||
localVarResponseHttpContentTypes.insert( utility::conversions::to_string_t("{{{mediaType}}}") );
|
|
||||||
{{/produces}}
|
|
||||||
|
|
||||||
utility::string_t localVarResponseHttpContentType;
|
|
||||||
|
|
||||||
// use JSON if possible
|
|
||||||
if ( localVarResponseHttpContentTypes.size() == 0 )
|
|
||||||
{
|
|
||||||
{{#vendorExtensions.x-codegen-response.isString}}
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("text/plain");
|
|
||||||
{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
{{^vendorExtensions.x-codegen-response.isString}}
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("application/json");
|
|
||||||
{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
}
|
|
||||||
// JSON
|
|
||||||
else if ( localVarResponseHttpContentTypes.find(utility::conversions::to_string_t("application/json")) != localVarResponseHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("application/json");
|
|
||||||
}
|
|
||||||
// multipart formdata
|
|
||||||
else if( localVarResponseHttpContentTypes.find(utility::conversions::to_string_t("multipart/form-data")) != localVarResponseHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("multipart/form-data");
|
|
||||||
}
|
|
||||||
{{#vendorExtensions.x-codegen-response.isString}}
|
|
||||||
// plain text
|
|
||||||
else if( localVarResponseHttpContentTypes.find(utility::conversions::to_string_t("text/plain")) != localVarResponseHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("text/plain");
|
|
||||||
}
|
|
||||||
{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
{{#vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//It's going to be binary, so just use the first one.
|
|
||||||
localVarResponseHttpContentType = *localVarResponseHttpContentTypes.begin();
|
|
||||||
}
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
{{^vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("{{classname}}->{{operationId}} does not produce any supported media type"));
|
|
||||||
}
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
|
|
||||||
localVarHeaderParams[utility::conversions::to_string_t("Accept")] = localVarResponseHttpContentType;
|
|
||||||
|
|
||||||
std::unordered_set<utility::string_t> localVarConsumeHttpContentTypes;
|
|
||||||
{{#consumes}}
|
|
||||||
localVarConsumeHttpContentTypes.insert( utility::conversions::to_string_t("{{{mediaType}}}") );
|
|
||||||
{{/consumes}}
|
|
||||||
|
|
||||||
{{#allParams}}
|
|
||||||
{{^isBodyParam}}
|
|
||||||
{{^isPathParam}}
|
|
||||||
{{#required}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{^isContainer}}
|
|
||||||
if ({{paramName}} != nullptr)
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{/required}}
|
|
||||||
{{^required}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{^isContainer}}
|
|
||||||
if ({{paramName}} && *{{paramName}} != nullptr)
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
{{#isFile}}
|
|
||||||
if ({{paramName}} && *{{paramName}} != nullptr)
|
|
||||||
{{/isFile}}
|
|
||||||
{{^isFile}}
|
|
||||||
if ({{paramName}})
|
|
||||||
{{/isFile}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{#isContainer}}
|
|
||||||
if ({{paramName}})
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/required}}
|
|
||||||
{
|
|
||||||
{{#isQueryParam}}
|
|
||||||
localVarQueryParams[utility::conversions::to_string_t("{{baseName}}")] = ApiClient::parameterToString({{^required}}*{{/required}}{{paramName}});
|
|
||||||
{{/isQueryParam}}
|
|
||||||
{{#isHeaderParam}}
|
|
||||||
localVarHeaderParams[utility::conversions::to_string_t("{{baseName}}")] = ApiClient::parameterToString({{^required}}*{{/required}}{{paramName}});
|
|
||||||
{{/isHeaderParam}}
|
|
||||||
{{#isFormParam}}
|
|
||||||
{{#isFile}}
|
|
||||||
localVarFileParams[ utility::conversions::to_string_t("{{baseName}}") ] = {{^required}}*{{/required}}{{paramName}};
|
|
||||||
{{/isFile}}
|
|
||||||
{{^isFile}}
|
|
||||||
localVarFormParams[ utility::conversions::to_string_t("{{baseName}}") ] = ApiClient::parameterToString({{^required}}*{{/required}}{{paramName}});
|
|
||||||
{{/isFile}}
|
|
||||||
{{/isFormParam}}
|
|
||||||
}
|
|
||||||
{{/isPathParam}}
|
|
||||||
{{/isBodyParam}}
|
|
||||||
{{/allParams}}
|
|
||||||
|
|
||||||
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
||||||
// Apply customHeaders AFTER all other headers (so they can override)
|
|
||||||
for (const auto& header : customHeaders) {
|
|
||||||
localVarHeaderParams[header.first] = header.second;
|
|
||||||
}
|
|
||||||
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
||||||
|
|
||||||
std::shared_ptr<IHttpBody> localVarHttpBody;
|
|
||||||
utility::string_t localVarRequestHttpContentType;
|
|
||||||
|
|
||||||
// use JSON if possible
|
|
||||||
if ( localVarConsumeHttpContentTypes.size() == 0 || localVarConsumeHttpContentTypes.find(utility::conversions::to_string_t("application/json")) != localVarConsumeHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarRequestHttpContentType = utility::conversions::to_string_t("application/json");
|
|
||||||
{{#bodyParam}}
|
|
||||||
web::json::value localVarJson;
|
|
||||||
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
localVarJson = ModelBase::toJson({{paramName}}{{^required}}.get(){{/required}});
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{#isArray}}
|
|
||||||
{
|
|
||||||
std::vector<web::json::value> localVarJsonArray;
|
|
||||||
for( auto& localVarItem : {{paramName}}{{^required}}.get(){{/required}} )
|
|
||||||
{
|
|
||||||
{{#items.isPrimitiveType}}localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
{{/items.isPrimitiveType}}{{^items.isPrimitiveType}}{{#items.isString}}localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
{{/items.isString}}{{^items.isString}}{{#items.isDateTime}}localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
{{/items.isDateTime}}{{^items.isDateTime}}localVarJsonArray.push_back( localVarItem.get() ? localVarItem->toJson() : web::json::value::null() );
|
|
||||||
{{/items.isDateTime}}{{/items.isString}}{{/items.isPrimitiveType}}
|
|
||||||
}
|
|
||||||
localVarJson = web::json::value::array(localVarJsonArray);
|
|
||||||
}
|
|
||||||
{{/isArray}}
|
|
||||||
{{^isArray}}{{#required}}localVarJson = ModelBase::toJson({{paramName}});
|
|
||||||
{{/required}}{{^required}}if ({{paramName}})
|
|
||||||
localVarJson = ModelBase::toJson(*{{paramName}});{{/required}}
|
|
||||||
{{/isArray}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
|
|
||||||
localVarHttpBody = std::shared_ptr<IHttpBody>( new JsonBody( localVarJson ) );
|
|
||||||
{{/bodyParam}}
|
|
||||||
}
|
|
||||||
// multipart formdata
|
|
||||||
else if( localVarConsumeHttpContentTypes.find(utility::conversions::to_string_t("multipart/form-data")) != localVarConsumeHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarRequestHttpContentType = utility::conversions::to_string_t("multipart/form-data");
|
|
||||||
{{#bodyParam}}
|
|
||||||
std::shared_ptr<MultipartFormData> localVarMultipart(new MultipartFormData);
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), {{paramName}}{{^required}}.get(){{/required}}));
|
|
||||||
{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isArray}}
|
|
||||||
{
|
|
||||||
std::vector<web::json::value> localVarJsonArray;
|
|
||||||
for( auto& localVarItem : {{paramName}}{{^required}}.get(){{/required}} )
|
|
||||||
{
|
|
||||||
localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
}
|
|
||||||
localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), localVarJsonArray, utility::conversions::to_string_t("application/json")));
|
|
||||||
}{{/isArray}}{{#isMap}}
|
|
||||||
{
|
|
||||||
std::map<utility::string_t, web::json::value> localVarJsonMap;
|
|
||||||
for( auto& localVarItem : {{paramName}}{{^required}}.get(){{/required}} )
|
|
||||||
{
|
|
||||||
web::json::value jval;
|
|
||||||
localVarJsonMap.insert( std::pair<utility::string_t, web::json::value>(localVarItem.first, ModelBase::toJson(localVarItem.second) ));
|
|
||||||
}
|
|
||||||
localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), localVarJsonMap, utility::conversions::to_string_t("application/json")));
|
|
||||||
}{{/isMap}}
|
|
||||||
{{^isArray}}{{^isMap}}{{#isString}}localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), {{paramName}}));
|
|
||||||
{{/isString}}{{^isString}}if({{^required}}{{paramName}} && (*{{paramName}}){{/required}}{{#required}}{{paramName}}{{/required}}.get())
|
|
||||||
{
|
|
||||||
{{^required}}(*{{/required}}{{paramName}}{{^required}}){{/required}}->toMultipart(localVarMultipart, utility::conversions::to_string_t("{{paramName}}"));
|
|
||||||
}
|
|
||||||
{{/isString}}
|
|
||||||
{{/isMap}}{{/isArray}}{{/isPrimitiveType}}
|
|
||||||
|
|
||||||
localVarHttpBody = localVarMultipart;
|
|
||||||
localVarRequestHttpContentType += utility::conversions::to_string_t("; boundary=") + localVarMultipart->getBoundary();
|
|
||||||
{{/bodyParam}}
|
|
||||||
}
|
|
||||||
else if (localVarConsumeHttpContentTypes.find(utility::conversions::to_string_t("application/x-www-form-urlencoded")) != localVarConsumeHttpContentTypes.end())
|
|
||||||
{
|
|
||||||
localVarRequestHttpContentType = utility::conversions::to_string_t("application/x-www-form-urlencoded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ApiException(415, utility::conversions::to_string_t("{{classname}}->{{operationId}} does not consume any supported media type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#authMethods}}
|
|
||||||
// authentication ({{name}}) required
|
|
||||||
{{#isApiKey}}
|
|
||||||
{{#isKeyInHeader}}
|
|
||||||
{
|
|
||||||
utility::string_t localVarApiKey = localVarApiConfiguration->getApiKey(utility::conversions::to_string_t("{{keyParamName}}"));
|
|
||||||
if ( localVarApiKey.size() > 0 )
|
|
||||||
{
|
|
||||||
localVarHeaderParams[utility::conversions::to_string_t("{{keyParamName}}")] = localVarApiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isKeyInHeader}}
|
|
||||||
{{#isKeyInQuery}}
|
|
||||||
{
|
|
||||||
utility::string_t localVarApiKey = localVarApiConfiguration->getApiKey(utility::conversions::to_string_t("{{keyParamName}}"));
|
|
||||||
if ( localVarApiKey.size() > 0 )
|
|
||||||
{
|
|
||||||
localVarQueryParams[utility::conversions::to_string_t("{{keyParamName}}")] = localVarApiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isKeyInQuery}}
|
|
||||||
{{/isApiKey}}
|
|
||||||
{{#isBasicBasic}}
|
|
||||||
// Basic authentication is added automatically as part of the http_client_config
|
|
||||||
{{/isBasicBasic}}
|
|
||||||
{{#isOAuth}}
|
|
||||||
// oauth2 authentication is added automatically as part of the http_client_config
|
|
||||||
{{/isOAuth}}
|
|
||||||
{{/authMethods}}
|
|
||||||
|
|
||||||
return m_ApiClient->callApi(localVarPath, utility::conversions::to_string_t("{{httpMethod}}"), localVarQueryParams, localVarHttpBody, localVarHeaderParams, localVarFormParams, localVarFileParams, localVarRequestHttpContentType)
|
|
||||||
.then([=, this](web::http::http_response localVarResponse)
|
|
||||||
{
|
|
||||||
if (m_ApiClient->getResponseHandler())
|
|
||||||
{
|
|
||||||
m_ApiClient->getResponseHandler()(localVarResponse.status_code(), localVarResponse.headers());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1xx - informational : OK
|
|
||||||
// 2xx - successful : OK
|
|
||||||
// 3xx - redirection : OK
|
|
||||||
// 4xx - client error : not OK
|
|
||||||
// 5xx - client error : not OK
|
|
||||||
if (localVarResponse.status_code() >= 400)
|
|
||||||
{
|
|
||||||
throw ApiException(localVarResponse.status_code()
|
|
||||||
, utility::conversions::to_string_t("error calling {{operationId}}: ") + localVarResponse.reason_phrase()
|
|
||||||
, std::make_shared<std::stringstream>(localVarResponse.extract_utf8string(true).get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// check response content type
|
|
||||||
if(localVarResponse.headers().has(utility::conversions::to_string_t("Content-Type")))
|
|
||||||
{
|
|
||||||
utility::string_t localVarContentType = localVarResponse.headers()[utility::conversions::to_string_t("Content-Type")];
|
|
||||||
if( localVarContentType.find(localVarResponseHttpContentType) == std::string::npos )
|
|
||||||
{
|
|
||||||
throw ApiException(500
|
|
||||||
, utility::conversions::to_string_t("error calling {{operationId}}: unexpected response type: ") + localVarContentType
|
|
||||||
, std::make_shared<std::stringstream>(localVarResponse.extract_utf8string(true).get()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
return localVarResponse.extract_vector();
|
|
||||||
})
|
|
||||||
.then([=, this](std::vector<unsigned char> localVarResponse)
|
|
||||||
{
|
|
||||||
{{{returnType}}} localVarResult = std::make_shared<HttpContent>();
|
|
||||||
std::shared_ptr<std::stringstream> stream = std::make_shared<std::stringstream>(std::string(localVarResponse.begin(), localVarResponse.end()));
|
|
||||||
localVarResult->setData(stream);
|
|
||||||
return localVarResult;
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
{{^vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
return localVarResponse.extract_string();
|
|
||||||
})
|
|
||||||
.then([=, this](utility::string_t localVarResponse)
|
|
||||||
{
|
|
||||||
{{^returnType}}
|
|
||||||
return void();
|
|
||||||
{{/returnType}}
|
|
||||||
{{#returnType}}
|
|
||||||
{{#returnContainer}}
|
|
||||||
{{{returnType}}} localVarResult;
|
|
||||||
{{/returnContainer}}
|
|
||||||
{{^returnContainer}}
|
|
||||||
{{{returnType}}} localVarResult({{{defaultResponse}}});
|
|
||||||
{{/returnContainer}}
|
|
||||||
|
|
||||||
if(localVarResponseHttpContentType == utility::conversions::to_string_t("application/json"))
|
|
||||||
{
|
|
||||||
web::json::value localVarJson = web::json::value::parse(localVarResponse);
|
|
||||||
{{#isArray}}
|
|
||||||
for( auto& localVarItem : localVarJson.as_array() )
|
|
||||||
{
|
|
||||||
{{{vendorExtensions.x-codegen-response.items.datatype}}} localVarItemObj;
|
|
||||||
ModelBase::fromJson(localVarItem, localVarItemObj);
|
|
||||||
localVarResult.push_back(localVarItemObj);
|
|
||||||
}{{/isArray}}{{#isMap}}
|
|
||||||
for( auto& localVarItem : localVarJson.as_object() )
|
|
||||||
{
|
|
||||||
{{{vendorExtensions.x-codegen-response.items.datatype}}} localVarItemObj;
|
|
||||||
ModelBase::fromJson(localVarItem.second, localVarItemObj);
|
|
||||||
localVarResult[localVarItem.first] = localVarItemObj;
|
|
||||||
}{{/isMap}}{{^isArray}}{{^isMap}}
|
|
||||||
ModelBase::fromJson(localVarJson, localVarResult);{{/isMap}}{{/isArray}}
|
|
||||||
}{{#vendorExtensions.x-codegen-response.isString}}
|
|
||||||
else if(localVarResponseHttpContentType == utility::conversions::to_string_t("text/plain"))
|
|
||||||
{
|
|
||||||
localVarResult = localVarResponse;
|
|
||||||
}{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
// else if(localVarResponseHttpContentType == utility::conversions::to_string_t("multipart/form-data"))
|
|
||||||
// {
|
|
||||||
// TODO multipart response parsing
|
|
||||||
// }
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ApiException(500
|
|
||||||
, utility::conversions::to_string_t("error calling {{operationId}}: unsupported response type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return localVarResult;
|
|
||||||
{{/returnType}}
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{{/operation}}
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
{{/operations}}
|
|
||||||
|
|
@ -1,368 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#operations}}
|
|
||||||
|
|
||||||
#include "{{packageName}}/api/{{classname}}.h"
|
|
||||||
#include "{{packageName}}/IHttpBody.h"
|
|
||||||
#include "{{packageName}}/JsonBody.h"
|
|
||||||
#include "{{packageName}}/MultipartFormData.h"
|
|
||||||
|
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
|
||||||
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
{{classname}}::{{classname}}( std::shared_ptr<const ApiClient> apiClient )
|
|
||||||
: m_ApiClient(apiClient)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::~{{classname}}()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#operation}}
|
|
||||||
pplx::task<{{{returnType}}}{{^returnType}}void{{/returnType}}> {{classname}}::{{operationId}}({{#allParams}}{{^required}}boost::optional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) const
|
|
||||||
{
|
|
||||||
{{#allParams}}{{#required}}{{^isPrimitiveType}}{{^isContainer}}
|
|
||||||
// verify the required parameter '{{paramName}}' is set
|
|
||||||
if ({{paramName}} == nullptr)
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}"));
|
|
||||||
}
|
|
||||||
{{/isContainer}}{{/isPrimitiveType}}{{/required}}{{/allParams}}
|
|
||||||
|
|
||||||
std::shared_ptr<const ApiConfiguration> localVarApiConfiguration( m_ApiClient->getConfiguration() );
|
|
||||||
utility::string_t localVarPath = utility::conversions::to_string_t("{{{path}}}");
|
|
||||||
{{#pathParams}}
|
|
||||||
boost::replace_all(localVarPath, utility::conversions::to_string_t("{") + utility::conversions::to_string_t("{{baseName}}") + utility::conversions::to_string_t("}"), web::uri::encode_uri(ApiClient::parameterToString({{{paramName}}})));
|
|
||||||
{{/pathParams}}
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t> localVarQueryParams;
|
|
||||||
std::map<utility::string_t, utility::string_t> localVarHeaderParams( localVarApiConfiguration->getDefaultHeaders() );
|
|
||||||
std::map<utility::string_t, utility::string_t> localVarFormParams;
|
|
||||||
std::map<utility::string_t, std::shared_ptr<HttpContent>> localVarFileParams;
|
|
||||||
|
|
||||||
std::unordered_set<utility::string_t> localVarResponseHttpContentTypes;
|
|
||||||
{{#produces}}
|
|
||||||
localVarResponseHttpContentTypes.insert( utility::conversions::to_string_t("{{{mediaType}}}") );
|
|
||||||
{{/produces}}
|
|
||||||
|
|
||||||
utility::string_t localVarResponseHttpContentType;
|
|
||||||
|
|
||||||
// use JSON if possible
|
|
||||||
if ( localVarResponseHttpContentTypes.size() == 0 )
|
|
||||||
{
|
|
||||||
{{#vendorExtensions.x-codegen-response.isString}}
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("text/plain");
|
|
||||||
{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
{{^vendorExtensions.x-codegen-response.isString}}
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("application/json");
|
|
||||||
{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
}
|
|
||||||
// JSON
|
|
||||||
else if ( localVarResponseHttpContentTypes.find(utility::conversions::to_string_t("application/json")) != localVarResponseHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("application/json");
|
|
||||||
}
|
|
||||||
// multipart formdata
|
|
||||||
else if( localVarResponseHttpContentTypes.find(utility::conversions::to_string_t("multipart/form-data")) != localVarResponseHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("multipart/form-data");
|
|
||||||
}
|
|
||||||
{{#vendorExtensions.x-codegen-response.isString}}
|
|
||||||
// plain text
|
|
||||||
else if( localVarResponseHttpContentTypes.find(utility::conversions::to_string_t("text/plain")) != localVarResponseHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarResponseHttpContentType = utility::conversions::to_string_t("text/plain");
|
|
||||||
}
|
|
||||||
{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
{{#vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//It's going to be binary, so just use the first one.
|
|
||||||
localVarResponseHttpContentType = *localVarResponseHttpContentTypes.begin();
|
|
||||||
}
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
{{^vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("{{classname}}->{{operationId}} does not produce any supported media type"));
|
|
||||||
}
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
|
|
||||||
localVarHeaderParams[utility::conversions::to_string_t("Accept")] = localVarResponseHttpContentType;
|
|
||||||
|
|
||||||
std::unordered_set<utility::string_t> localVarConsumeHttpContentTypes;
|
|
||||||
{{#consumes}}
|
|
||||||
localVarConsumeHttpContentTypes.insert( utility::conversions::to_string_t("{{{mediaType}}}") );
|
|
||||||
{{/consumes}}
|
|
||||||
|
|
||||||
{{#allParams}}
|
|
||||||
{{^isBodyParam}}
|
|
||||||
{{^isPathParam}}
|
|
||||||
{{#required}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{^isContainer}}
|
|
||||||
if ({{paramName}} != nullptr)
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{/required}}
|
|
||||||
{{^required}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{^isContainer}}
|
|
||||||
if ({{paramName}} && *{{paramName}} != nullptr)
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
{{#isFile}}
|
|
||||||
if ({{paramName}} && *{{paramName}} != nullptr)
|
|
||||||
{{/isFile}}
|
|
||||||
{{^isFile}}
|
|
||||||
if ({{paramName}})
|
|
||||||
{{/isFile}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{#isContainer}}
|
|
||||||
if ({{paramName}})
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/required}}
|
|
||||||
{
|
|
||||||
{{#isQueryParam}}
|
|
||||||
localVarQueryParams[utility::conversions::to_string_t("{{baseName}}")] = ApiClient::parameterToString({{^required}}*{{/required}}{{paramName}});
|
|
||||||
{{/isQueryParam}}
|
|
||||||
{{#isHeaderParam}}
|
|
||||||
localVarHeaderParams[utility::conversions::to_string_t("{{baseName}}")] = ApiClient::parameterToString({{^required}}*{{/required}}{{paramName}});
|
|
||||||
{{/isHeaderParam}}
|
|
||||||
{{#isFormParam}}
|
|
||||||
{{#isFile}}
|
|
||||||
localVarFileParams[ utility::conversions::to_string_t("{{baseName}}") ] = {{^required}}*{{/required}}{{paramName}};
|
|
||||||
{{/isFile}}
|
|
||||||
{{^isFile}}
|
|
||||||
localVarFormParams[ utility::conversions::to_string_t("{{baseName}}") ] = ApiClient::parameterToString({{^required}}*{{/required}}{{paramName}});
|
|
||||||
{{/isFile}}
|
|
||||||
{{/isFormParam}}
|
|
||||||
}
|
|
||||||
{{/isPathParam}}
|
|
||||||
{{/isBodyParam}}
|
|
||||||
{{/allParams}}
|
|
||||||
|
|
||||||
std::shared_ptr<IHttpBody> localVarHttpBody;
|
|
||||||
utility::string_t localVarRequestHttpContentType;
|
|
||||||
|
|
||||||
// use JSON if possible
|
|
||||||
if ( localVarConsumeHttpContentTypes.size() == 0 || localVarConsumeHttpContentTypes.find(utility::conversions::to_string_t("application/json")) != localVarConsumeHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarRequestHttpContentType = utility::conversions::to_string_t("application/json");
|
|
||||||
{{#bodyParam}}
|
|
||||||
web::json::value localVarJson;
|
|
||||||
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
localVarJson = ModelBase::toJson({{paramName}}{{^required}}.get(){{/required}});
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{#isArray}}
|
|
||||||
{
|
|
||||||
std::vector<web::json::value> localVarJsonArray;
|
|
||||||
for( auto& localVarItem : {{paramName}}{{^required}}.get(){{/required}} )
|
|
||||||
{
|
|
||||||
{{#items.isPrimitiveType}}localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
{{/items.isPrimitiveType}}{{^items.isPrimitiveType}}{{#items.isString}}localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
{{/items.isString}}{{^items.isString}}{{#items.isDateTime}}localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
{{/items.isDateTime}}{{^items.isDateTime}}localVarJsonArray.push_back( localVarItem.get() ? localVarItem->toJson() : web::json::value::null() );
|
|
||||||
{{/items.isDateTime}}{{/items.isString}}{{/items.isPrimitiveType}}
|
|
||||||
}
|
|
||||||
localVarJson = web::json::value::array(localVarJsonArray);
|
|
||||||
}
|
|
||||||
{{/isArray}}
|
|
||||||
{{^isArray}}{{#required}}localVarJson = ModelBase::toJson({{paramName}});
|
|
||||||
{{/required}}{{^required}}if ({{paramName}})
|
|
||||||
localVarJson = ModelBase::toJson(*{{paramName}});{{/required}}
|
|
||||||
{{/isArray}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
|
|
||||||
localVarHttpBody = std::shared_ptr<IHttpBody>( new JsonBody( localVarJson ) );
|
|
||||||
{{/bodyParam}}
|
|
||||||
}
|
|
||||||
// multipart formdata
|
|
||||||
else if( localVarConsumeHttpContentTypes.find(utility::conversions::to_string_t("multipart/form-data")) != localVarConsumeHttpContentTypes.end() )
|
|
||||||
{
|
|
||||||
localVarRequestHttpContentType = utility::conversions::to_string_t("multipart/form-data");
|
|
||||||
{{#bodyParam}}
|
|
||||||
std::shared_ptr<MultipartFormData> localVarMultipart(new MultipartFormData);
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), {{paramName}}{{^required}}.get(){{/required}}));
|
|
||||||
{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isArray}}
|
|
||||||
{
|
|
||||||
std::vector<web::json::value> localVarJsonArray;
|
|
||||||
for( auto& localVarItem : {{paramName}}{{^required}}.get(){{/required}} )
|
|
||||||
{
|
|
||||||
localVarJsonArray.push_back(ModelBase::toJson(localVarItem));
|
|
||||||
}
|
|
||||||
localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), localVarJsonArray, utility::conversions::to_string_t("application/json")));
|
|
||||||
}{{/isArray}}{{#isMap}}
|
|
||||||
{
|
|
||||||
std::map<utility::string_t, web::json::value> localVarJsonMap;
|
|
||||||
for( auto& localVarItem : {{paramName}}{{^required}}.get(){{/required}} )
|
|
||||||
{
|
|
||||||
web::json::value jval;
|
|
||||||
localVarJsonMap.insert( std::pair<utility::string_t, web::json::value>(localVarItem.first, ModelBase::toJson(localVarItem.second) ));
|
|
||||||
}
|
|
||||||
localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), localVarJsonMap, utility::conversions::to_string_t("application/json")));
|
|
||||||
}{{/isMap}}
|
|
||||||
{{^isArray}}{{^isMap}}{{#isString}}localVarMultipart->add(ModelBase::toHttpContent(utility::conversions::to_string_t("{{paramName}}"), {{paramName}}));
|
|
||||||
{{/isString}}{{^isString}}if({{^required}}{{paramName}} && (*{{paramName}}){{/required}}{{#required}}{{paramName}}{{/required}}.get())
|
|
||||||
{
|
|
||||||
{{^required}}(*{{/required}}{{paramName}}{{^required}}){{/required}}->toMultipart(localVarMultipart, utility::conversions::to_string_t("{{paramName}}"));
|
|
||||||
}
|
|
||||||
{{/isString}}
|
|
||||||
{{/isMap}}{{/isArray}}{{/isPrimitiveType}}
|
|
||||||
|
|
||||||
localVarHttpBody = localVarMultipart;
|
|
||||||
localVarRequestHttpContentType += utility::conversions::to_string_t("; boundary=") + localVarMultipart->getBoundary();
|
|
||||||
{{/bodyParam}}
|
|
||||||
}
|
|
||||||
else if (localVarConsumeHttpContentTypes.find(utility::conversions::to_string_t("application/x-www-form-urlencoded")) != localVarConsumeHttpContentTypes.end())
|
|
||||||
{
|
|
||||||
localVarRequestHttpContentType = utility::conversions::to_string_t("application/x-www-form-urlencoded");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ApiException(415, utility::conversions::to_string_t("{{classname}}->{{operationId}} does not consume any supported media type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#authMethods}}
|
|
||||||
// authentication ({{name}}) required
|
|
||||||
{{#isApiKey}}
|
|
||||||
{{#isKeyInHeader}}
|
|
||||||
{
|
|
||||||
utility::string_t localVarApiKey = localVarApiConfiguration->getApiKey(utility::conversions::to_string_t("{{keyParamName}}"));
|
|
||||||
if ( localVarApiKey.size() > 0 )
|
|
||||||
{
|
|
||||||
localVarHeaderParams[utility::conversions::to_string_t("{{keyParamName}}")] = localVarApiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isKeyInHeader}}
|
|
||||||
{{#isKeyInQuery}}
|
|
||||||
{
|
|
||||||
utility::string_t localVarApiKey = localVarApiConfiguration->getApiKey(utility::conversions::to_string_t("{{keyParamName}}"));
|
|
||||||
if ( localVarApiKey.size() > 0 )
|
|
||||||
{
|
|
||||||
localVarQueryParams[utility::conversions::to_string_t("{{keyParamName}}")] = localVarApiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isKeyInQuery}}
|
|
||||||
{{/isApiKey}}
|
|
||||||
{{#isBasicBasic}}
|
|
||||||
// Basic authentication is added automatically as part of the http_client_config
|
|
||||||
{{/isBasicBasic}}
|
|
||||||
{{#isOAuth}}
|
|
||||||
// oauth2 authentication is added automatically as part of the http_client_config
|
|
||||||
{{/isOAuth}}
|
|
||||||
{{/authMethods}}
|
|
||||||
|
|
||||||
return m_ApiClient->callApi(localVarPath, utility::conversions::to_string_t("{{httpMethod}}"), localVarQueryParams, localVarHttpBody, localVarHeaderParams, localVarFormParams, localVarFileParams, localVarRequestHttpContentType)
|
|
||||||
.then([=, this](web::http::http_response localVarResponse)
|
|
||||||
{
|
|
||||||
if (m_ApiClient->getResponseHandler())
|
|
||||||
{
|
|
||||||
m_ApiClient->getResponseHandler()(localVarResponse.status_code(), localVarResponse.headers());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1xx - informational : OK
|
|
||||||
// 2xx - successful : OK
|
|
||||||
// 3xx - redirection : OK
|
|
||||||
// 4xx - client error : not OK
|
|
||||||
// 5xx - client error : not OK
|
|
||||||
if (localVarResponse.status_code() >= 400)
|
|
||||||
{
|
|
||||||
throw ApiException(localVarResponse.status_code()
|
|
||||||
, utility::conversions::to_string_t("error calling {{operationId}}: ") + localVarResponse.reason_phrase()
|
|
||||||
, std::make_shared<std::stringstream>(localVarResponse.extract_utf8string(true).get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// check response content type
|
|
||||||
if(localVarResponse.headers().has(utility::conversions::to_string_t("Content-Type")))
|
|
||||||
{
|
|
||||||
utility::string_t localVarContentType = localVarResponse.headers()[utility::conversions::to_string_t("Content-Type")];
|
|
||||||
if( localVarContentType.find(localVarResponseHttpContentType) == std::string::npos )
|
|
||||||
{
|
|
||||||
throw ApiException(500
|
|
||||||
, utility::conversions::to_string_t("error calling {{operationId}}: unexpected response type: ") + localVarContentType
|
|
||||||
, std::make_shared<std::stringstream>(localVarResponse.extract_utf8string(true).get()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
return localVarResponse.extract_vector();
|
|
||||||
})
|
|
||||||
.then([=, this](std::vector<unsigned char> localVarResponse)
|
|
||||||
{
|
|
||||||
{{{returnType}}} localVarResult = std::make_shared<HttpContent>();
|
|
||||||
std::shared_ptr<std::stringstream> stream = std::make_shared<std::stringstream>(std::string(localVarResponse.begin(), localVarResponse.end()));
|
|
||||||
localVarResult->setData(stream);
|
|
||||||
return localVarResult;
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
{{^vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
return localVarResponse.extract_string();
|
|
||||||
})
|
|
||||||
.then([=, this](utility::string_t localVarResponse)
|
|
||||||
{
|
|
||||||
{{^returnType}}
|
|
||||||
return void();
|
|
||||||
{{/returnType}}
|
|
||||||
{{#returnType}}
|
|
||||||
{{#returnContainer}}
|
|
||||||
{{{returnType}}} localVarResult;
|
|
||||||
{{/returnContainer}}
|
|
||||||
{{^returnContainer}}
|
|
||||||
{{{returnType}}} localVarResult({{{defaultResponse}}});
|
|
||||||
{{/returnContainer}}
|
|
||||||
|
|
||||||
if(localVarResponseHttpContentType == utility::conversions::to_string_t("application/json"))
|
|
||||||
{
|
|
||||||
web::json::value localVarJson = web::json::value::parse(localVarResponse);
|
|
||||||
{{#isArray}}
|
|
||||||
for( auto& localVarItem : localVarJson.as_array() )
|
|
||||||
{
|
|
||||||
{{{vendorExtensions.x-codegen-response.items.datatype}}} localVarItemObj;
|
|
||||||
ModelBase::fromJson(localVarItem, localVarItemObj);
|
|
||||||
localVarResult.push_back(localVarItemObj);
|
|
||||||
}{{/isArray}}{{#isMap}}
|
|
||||||
for( auto& localVarItem : localVarJson.as_object() )
|
|
||||||
{
|
|
||||||
{{{vendorExtensions.x-codegen-response.items.datatype}}} localVarItemObj;
|
|
||||||
ModelBase::fromJson(localVarItem.second, localVarItemObj);
|
|
||||||
localVarResult[localVarItem.first] = localVarItemObj;
|
|
||||||
}{{/isMap}}{{^isArray}}{{^isMap}}
|
|
||||||
ModelBase::fromJson(localVarJson, localVarResult);{{/isMap}}{{/isArray}}
|
|
||||||
}{{#vendorExtensions.x-codegen-response.isString}}
|
|
||||||
else if(localVarResponseHttpContentType == utility::conversions::to_string_t("text/plain"))
|
|
||||||
{
|
|
||||||
localVarResult = localVarResponse;
|
|
||||||
}{{/vendorExtensions.x-codegen-response.isString}}
|
|
||||||
// else if(localVarResponseHttpContentType == utility::conversions::to_string_t("multipart/form-data"))
|
|
||||||
// {
|
|
||||||
// TODO multipart response parsing
|
|
||||||
// }
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw ApiException(500
|
|
||||||
, utility::conversions::to_string_t("error calling {{operationId}}: unsupported response type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return localVarResult;
|
|
||||||
{{/returnType}}
|
|
||||||
{{/vendorExtensions.x-codegen-response-ishttpcontent}}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{{/operation}}
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
{{/operations}}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* ApiClient.h
|
|
||||||
*
|
|
||||||
* This is an API client responsible for stating the HTTP calls
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{apiHeaderGuardPrefix}}_ApiClient_H_
|
|
||||||
#define {{apiHeaderGuardPrefix}}_ApiClient_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include "{{packageName}}/ApiConfiguration.h"
|
|
||||||
#include "{{packageName}}/ApiException.h"
|
|
||||||
#include "{{packageName}}/IHttpBody.h"
|
|
||||||
#include "{{packageName}}/HttpContent.h"
|
|
||||||
{{^hasModelImport}}
|
|
||||||
#include "{{packageName}}/ModelBase.h"
|
|
||||||
{{/hasModelImport}}
|
|
||||||
#if defined (_WIN32) || defined (_WIN64)
|
|
||||||
#undef U
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
#include <cpprest/http_client.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
class {{declspec}} ApiClient
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ApiClient( std::shared_ptr<const ApiConfiguration> configuration = nullptr );
|
|
||||||
virtual ~ApiClient();
|
|
||||||
|
|
||||||
typedef std::function<void(web::http::status_code, const web::http::http_headers&)> ResponseHandlerType;
|
|
||||||
|
|
||||||
const ResponseHandlerType& getResponseHandler() const;
|
|
||||||
void setResponseHandler(const ResponseHandlerType& responseHandler);
|
|
||||||
|
|
||||||
std::shared_ptr<const ApiConfiguration> getConfiguration() const;
|
|
||||||
void setConfiguration(std::shared_ptr<const ApiConfiguration> configuration);
|
|
||||||
|
|
||||||
static utility::string_t parameterToString(utility::string_t value);
|
|
||||||
static utility::string_t parameterToString(int32_t value);
|
|
||||||
static utility::string_t parameterToString(int64_t value);
|
|
||||||
static utility::string_t parameterToString(float value);
|
|
||||||
static utility::string_t parameterToString(double value);
|
|
||||||
static utility::string_t parameterToString(const utility::datetime &value);
|
|
||||||
static utility::string_t parameterToString(bool value);
|
|
||||||
{{^hasModelImport}}
|
|
||||||
static utility::string_t parameterToString(const ModelBase& value);
|
|
||||||
{{/hasModelImport}}
|
|
||||||
template<class T>
|
|
||||||
static utility::string_t parameterToString(const std::vector<T>& value);
|
|
||||||
template<class T>
|
|
||||||
static utility::string_t parameterToString(const std::shared_ptr<T>& value);
|
|
||||||
|
|
||||||
pplx::task<web::http::http_response> callApi(
|
|
||||||
const utility::string_t& path,
|
|
||||||
const utility::string_t& method,
|
|
||||||
const std::map<utility::string_t, utility::string_t>& queryParams,
|
|
||||||
const std::shared_ptr<IHttpBody> postBody,
|
|
||||||
const std::map<utility::string_t, utility::string_t>& headerParams,
|
|
||||||
const std::map<utility::string_t, utility::string_t>& formParams,
|
|
||||||
const std::map<utility::string_t, std::shared_ptr<HttpContent>>& fileParams,
|
|
||||||
const utility::string_t& contentType
|
|
||||||
) const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
ResponseHandlerType m_ResponseHandler;
|
|
||||||
std::shared_ptr<const ApiConfiguration> m_Configuration;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
utility::string_t ApiClient::parameterToString(const std::vector<T>& value)
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
|
|
||||||
for( size_t i = 0; i < value.size(); i++)
|
|
||||||
{
|
|
||||||
if( i > 0) ss << utility::conversions::to_string_t(", ");
|
|
||||||
ss << ApiClient::parameterToString(value[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
utility::string_t ApiClient::parameterToString(const std::shared_ptr<T>& value)
|
|
||||||
{
|
|
||||||
return parameterToString(*value.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{apiHeaderGuardPrefix}}_ApiClient_H_ */
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/ApiClient.h"
|
|
||||||
#include "{{packageName}}/MultipartFormData.h"
|
|
||||||
#include "{{packageName}}/ModelBase.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <limits>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
utility::string_t toString(const T value)
|
|
||||||
{
|
|
||||||
utility::ostringstream_t out;
|
|
||||||
out << std::setprecision(std::numeric_limits<T>::digits10) << std::fixed << value;
|
|
||||||
return out.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
using namespace {{modelNamespace}};
|
|
||||||
|
|
||||||
ApiClient::ApiClient(std::shared_ptr<const ApiConfiguration> configuration )
|
|
||||||
: m_Configuration(configuration)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
ApiClient::~ApiClient()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const ApiClient::ResponseHandlerType& ApiClient::getResponseHandler() const {
|
|
||||||
return m_ResponseHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiClient::setResponseHandler(const ResponseHandlerType& responseHandler) {
|
|
||||||
m_ResponseHandler = responseHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<const ApiConfiguration> ApiClient::getConfiguration() const
|
|
||||||
{
|
|
||||||
return m_Configuration;
|
|
||||||
}
|
|
||||||
void ApiClient::setConfiguration(std::shared_ptr<const ApiConfiguration> configuration)
|
|
||||||
{
|
|
||||||
m_Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
utility::string_t ApiClient::parameterToString(utility::string_t value)
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
utility::string_t ApiClient::parameterToString(int64_t value)
|
|
||||||
{
|
|
||||||
std::stringstream valueAsStringStream;
|
|
||||||
valueAsStringStream << value;
|
|
||||||
return utility::conversions::to_string_t(valueAsStringStream.str());
|
|
||||||
}
|
|
||||||
utility::string_t ApiClient::parameterToString(int32_t value)
|
|
||||||
{
|
|
||||||
std::stringstream valueAsStringStream;
|
|
||||||
valueAsStringStream << value;
|
|
||||||
return utility::conversions::to_string_t(valueAsStringStream.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t ApiClient::parameterToString(float value)
|
|
||||||
{
|
|
||||||
return utility::conversions::to_string_t(toString(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t ApiClient::parameterToString(double value)
|
|
||||||
{
|
|
||||||
return utility::conversions::to_string_t(toString(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t ApiClient::parameterToString(const utility::datetime &value)
|
|
||||||
{
|
|
||||||
return utility::conversions::to_string_t(value.to_string(utility::datetime::ISO_8601));
|
|
||||||
}
|
|
||||||
|
|
||||||
{{^hasModelImport}}
|
|
||||||
utility::string_t ApiClient::parameterToString(const ModelBase& value)
|
|
||||||
{
|
|
||||||
return value.toJson().serialize();
|
|
||||||
}
|
|
||||||
{{/hasModelImport}}
|
|
||||||
|
|
||||||
utility::string_t ApiClient::parameterToString(bool value)
|
|
||||||
{
|
|
||||||
std::stringstream valueAsStringStream;
|
|
||||||
valueAsStringStream << std::boolalpha << value;
|
|
||||||
return utility::conversions::to_string_t(valueAsStringStream.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
pplx::task<web::http::http_response> ApiClient::callApi(
|
|
||||||
const utility::string_t& path,
|
|
||||||
const utility::string_t& method,
|
|
||||||
const std::map<utility::string_t, utility::string_t>& queryParams,
|
|
||||||
const std::shared_ptr<IHttpBody> postBody,
|
|
||||||
const std::map<utility::string_t, utility::string_t>& headerParams,
|
|
||||||
const std::map<utility::string_t, utility::string_t>& formParams,
|
|
||||||
const std::map<utility::string_t, std::shared_ptr<HttpContent>>& fileParams,
|
|
||||||
const utility::string_t& contentType
|
|
||||||
) const
|
|
||||||
{
|
|
||||||
if (postBody != nullptr && formParams.size() != 0)
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("Cannot have body and form params"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postBody != nullptr && fileParams.size() != 0)
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("Cannot have body and file params"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileParams.size() > 0 && contentType != utility::conversions::to_string_t("multipart/form-data"))
|
|
||||||
{
|
|
||||||
throw ApiException(400, utility::conversions::to_string_t("Operations with file parameters must be called with multipart/form-data"));
|
|
||||||
}
|
|
||||||
|
|
||||||
web::http::client::http_client client(m_Configuration->getBaseUrl(), m_Configuration->getHttpConfig());
|
|
||||||
|
|
||||||
web::http::http_request request;
|
|
||||||
for (const auto& kvp : headerParams)
|
|
||||||
{
|
|
||||||
request.headers().add(kvp.first, kvp.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileParams.size() > 0)
|
|
||||||
{
|
|
||||||
MultipartFormData uploadData;
|
|
||||||
for (const auto& kvp : formParams)
|
|
||||||
{
|
|
||||||
uploadData.add(ModelBase::toHttpContent(kvp.first, kvp.second));
|
|
||||||
}
|
|
||||||
for (const auto& kvp : fileParams)
|
|
||||||
{
|
|
||||||
uploadData.add(ModelBase::toHttpContent(kvp.first, kvp.second));
|
|
||||||
}
|
|
||||||
std::stringstream data;
|
|
||||||
uploadData.writeTo(data);
|
|
||||||
auto bodyString = data.str();
|
|
||||||
const auto length = bodyString.size();
|
|
||||||
request.set_body(concurrency::streams::bytestream::open_istream(std::move(bodyString)), length, utility::conversions::to_string_t("multipart/form-data; boundary=") + uploadData.getBoundary());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (postBody != nullptr)
|
|
||||||
{
|
|
||||||
std::stringstream data;
|
|
||||||
postBody->writeTo(data);
|
|
||||||
auto bodyString = data.str();
|
|
||||||
const auto length = bodyString.size();
|
|
||||||
request.set_body(concurrency::streams::bytestream::open_istream(std::move(bodyString)), length, contentType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (contentType == utility::conversions::to_string_t("application/json"))
|
|
||||||
{
|
|
||||||
web::json::value body_data = web::json::value::object();
|
|
||||||
for (auto& kvp : formParams)
|
|
||||||
{
|
|
||||||
body_data[kvp.first] = ModelBase::toJson(kvp.second);
|
|
||||||
}
|
|
||||||
if (!formParams.empty())
|
|
||||||
{
|
|
||||||
request.set_body(body_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
web::http::uri_builder formData;
|
|
||||||
for (const auto& kvp : formParams)
|
|
||||||
{
|
|
||||||
formData.append_query(kvp.first, kvp.second);
|
|
||||||
}
|
|
||||||
if (!formParams.empty())
|
|
||||||
{
|
|
||||||
request.set_body(formData.query(), utility::conversions::to_string_t("application/x-www-form-urlencoded"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
web::http::uri_builder builder(path);
|
|
||||||
for (const auto& kvp : queryParams)
|
|
||||||
{
|
|
||||||
builder.append_query(kvp.first, kvp.second);
|
|
||||||
}
|
|
||||||
request.set_request_uri(builder.to_uri());
|
|
||||||
request.set_method(method);
|
|
||||||
if ( !request.headers().has( web::http::header_names::user_agent ) )
|
|
||||||
{
|
|
||||||
request.headers().add( web::http::header_names::user_agent, m_Configuration->getUserAgent() );
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.request(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* ApiConfiguration.h
|
|
||||||
*
|
|
||||||
* This class represents a single item of a multipart-formdata request.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{apiHeaderGuardPrefix}}_ApiConfiguration_H_
|
|
||||||
#define {{apiHeaderGuardPrefix}}_ApiConfiguration_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
#include <cpprest/http_client.h>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} ApiConfiguration
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ApiConfiguration();
|
|
||||||
virtual ~ApiConfiguration();
|
|
||||||
|
|
||||||
const web::http::client::http_client_config& getHttpConfig() const;
|
|
||||||
void setHttpConfig( web::http::client::http_client_config& value );
|
|
||||||
|
|
||||||
utility::string_t getBaseUrl() const;
|
|
||||||
void setBaseUrl( const utility::string_t value );
|
|
||||||
|
|
||||||
utility::string_t getUserAgent() const;
|
|
||||||
void setUserAgent( const utility::string_t value );
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t>& getDefaultHeaders();
|
|
||||||
const std::map<utility::string_t, utility::string_t>& getDefaultHeaders() const;
|
|
||||||
|
|
||||||
utility::string_t getApiKey( const utility::string_t& prefix) const;
|
|
||||||
void setApiKey( const utility::string_t& prefix, const utility::string_t& apiKey );
|
|
||||||
|
|
||||||
protected:
|
|
||||||
utility::string_t m_BaseUrl;
|
|
||||||
std::map<utility::string_t, utility::string_t> m_DefaultHeaders;
|
|
||||||
std::map<utility::string_t, utility::string_t> m_ApiKeys;
|
|
||||||
web::http::client::http_client_config m_HttpConfig;
|
|
||||||
utility::string_t m_UserAgent;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
#endif /* {{apiHeaderGuardPrefix}}_ApiConfiguration_H_ */
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/ApiConfiguration.h"
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
ApiConfiguration::ApiConfiguration()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiConfiguration::~ApiConfiguration()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const web::http::client::http_client_config& ApiConfiguration::getHttpConfig() const
|
|
||||||
{
|
|
||||||
return m_HttpConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiConfiguration::setHttpConfig( web::http::client::http_client_config& value )
|
|
||||||
{
|
|
||||||
m_HttpConfig = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t ApiConfiguration::getBaseUrl() const
|
|
||||||
{
|
|
||||||
return m_BaseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiConfiguration::setBaseUrl( const utility::string_t value )
|
|
||||||
{
|
|
||||||
m_BaseUrl = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t ApiConfiguration::getUserAgent() const
|
|
||||||
{
|
|
||||||
return m_UserAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiConfiguration::setUserAgent( const utility::string_t value )
|
|
||||||
{
|
|
||||||
m_UserAgent = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t>& ApiConfiguration::getDefaultHeaders()
|
|
||||||
{
|
|
||||||
return m_DefaultHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::map<utility::string_t, utility::string_t>& ApiConfiguration::getDefaultHeaders() const
|
|
||||||
{
|
|
||||||
return m_DefaultHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t ApiConfiguration::getApiKey( const utility::string_t& prefix) const
|
|
||||||
{
|
|
||||||
auto result = m_ApiKeys.find(prefix);
|
|
||||||
if( result != m_ApiKeys.end() )
|
|
||||||
{
|
|
||||||
return result->second;
|
|
||||||
}
|
|
||||||
return utility::conversions::to_string_t("");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiConfiguration::setApiKey( const utility::string_t& prefix, const utility::string_t& apiKey )
|
|
||||||
{
|
|
||||||
m_ApiKeys[prefix] = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* ApiException.h
|
|
||||||
*
|
|
||||||
* This is the exception being thrown in case the api call was not successful
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{apiHeaderGuardPrefix}}_ApiException_H_
|
|
||||||
#define {{apiHeaderGuardPrefix}}_ApiException_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
#include <cpprest/http_msg.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} ApiException
|
|
||||||
: public web::http::http_exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ApiException( int errorCode
|
|
||||||
, const utility::string_t& message
|
|
||||||
, std::shared_ptr<std::istream> content = nullptr );
|
|
||||||
ApiException( int errorCode
|
|
||||||
, const utility::string_t& message
|
|
||||||
, std::map<utility::string_t, utility::string_t>& headers
|
|
||||||
, std::shared_ptr<std::istream> content = nullptr );
|
|
||||||
virtual ~ApiException();
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t>& getHeaders();
|
|
||||||
std::shared_ptr<std::istream> getContent() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::shared_ptr<std::istream> m_Content;
|
|
||||||
std::map<utility::string_t, utility::string_t> m_Headers;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{apiHeaderGuardPrefix}}_ApiBase_H_ */
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/ApiException.h"
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
||||||
ApiException::ApiException( int errorCode
|
|
||||||
, const utility::string_t& message
|
|
||||||
, std::shared_ptr<std::istream> content /*= nullptr*/ )
|
|
||||||
: web::http::http_exception( errorCode, message )
|
|
||||||
, m_Content(content)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
ApiException::ApiException( int errorCode
|
|
||||||
, const utility::string_t& message
|
|
||||||
, std::map<utility::string_t, utility::string_t>& headers
|
|
||||||
, std::shared_ptr<std::istream> content /*= nullptr*/ )
|
|
||||||
: web::http::http_exception( errorCode, message )
|
|
||||||
, m_Content(content)
|
|
||||||
, m_Headers(headers)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiException::~ApiException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<std::istream> ApiException::getContent() const
|
|
||||||
{
|
|
||||||
return m_Content;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t>& ApiException::getHeaders()
|
|
||||||
{
|
|
||||||
return m_Headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#apiNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/apiNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
@PACKAGE_INIT@
|
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake)
|
|
||||||
|
|
||||||
check_required_components("@PROJECT_NAME@")
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
#
|
|
||||||
# {{{appName}}}
|
|
||||||
# {{{appDescription}}}
|
|
||||||
#
|
|
||||||
# The version of the OpenAPI document: 1.0.0
|
|
||||||
#
|
|
||||||
# https://openapi-generator.tech
|
|
||||||
#
|
|
||||||
# NOTE: Auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
||||||
|
|
||||||
cmake_minimum_required (VERSION 3.10)
|
|
||||||
|
|
||||||
project({{{packageName}}} CXX)
|
|
||||||
|
|
||||||
# Force -fPIC even if the project is configured for building a static library.
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
set(CXX_STANDARD_REQUIRED ON)
|
|
||||||
if(NOT CMAKE_CXX_STANDARD)
|
|
||||||
if(DEFINED CMAKE_CXX20_STANDARD_COMPILE_OPTION OR
|
|
||||||
DEFINED CMAKE_CXX20_EXTENSION_COMPILE_OPTION)
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
elseif(DEFINED CMAKE_CXX17_STANDARD_COMPILE_OPTION OR
|
|
||||||
DEFINED CMAKE_CXX17_EXTENSION_COMPILE_OPTION)
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
elseif(DEFINED CMAKE_CXX14_STANDARD_COMPILE_OPTION OR
|
|
||||||
DEFINED CMAKE_CXX14_EXTENSION_COMPILE_OPTION)
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
|
||||||
else()
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
|
||||||
set(CMAKE_BUILD_TYPE Release)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(cpprestsdk REQUIRED)
|
|
||||||
target_compile_definitions(cpprestsdk::cpprest INTERFACE _TURN_OFF_PLATFORM_STRING)
|
|
||||||
find_package(Boost REQUIRED)
|
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
|
||||||
include(CMakePackageConfigHelpers)
|
|
||||||
|
|
||||||
file(GLOB_RECURSE HEADER_FILES "include/*.h")
|
|
||||||
file(GLOB_RECURSE SOURCE_FILES "src/*.cpp")
|
|
||||||
|
|
||||||
add_library(${PROJECT_NAME} ${HEADER_FILES} ${SOURCE_FILES})
|
|
||||||
|
|
||||||
target_compile_options(${PROJECT_NAME}
|
|
||||||
PRIVATE
|
|
||||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
|
||||||
-Wall -Wno-unused-variable -Wno-unused-lambda-capture>
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME}
|
|
||||||
PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::headers cpprestsdk::cpprest)
|
|
||||||
|
|
||||||
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
|
||||||
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
TARGETS ${PROJECT_NAME}
|
|
||||||
EXPORT ${PROJECT_NAME}Targets
|
|
||||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
||||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
||||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
|
||||||
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME}
|
|
||||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
|
||||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
EXPORT ${PROJECT_NAME}Targets
|
|
||||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
|
||||||
)
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
|
|
||||||
#
|
|
||||||
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
|
|
||||||
|
|
||||||
git_user_id=$1
|
|
||||||
git_repo_id=$2
|
|
||||||
release_note=$3
|
|
||||||
git_host=$4
|
|
||||||
|
|
||||||
if [ "$git_host" = "" ]; then
|
|
||||||
git_host="{{{gitHost}}}"
|
|
||||||
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$git_user_id" = "" ]; then
|
|
||||||
git_user_id="{{{gitUserId}}}"
|
|
||||||
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$git_repo_id" = "" ]; then
|
|
||||||
git_repo_id="{{{gitRepoId}}}"
|
|
||||||
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$release_note" = "" ]; then
|
|
||||||
release_note="{{{releaseNote}}}"
|
|
||||||
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Initialize the local directory as a Git repository
|
|
||||||
git init
|
|
||||||
|
|
||||||
# Adds the files in the local repository and stages them for commit.
|
|
||||||
git add .
|
|
||||||
|
|
||||||
# Commits the tracked changes and prepares them to be pushed to a remote repository.
|
|
||||||
git commit -m "$release_note"
|
|
||||||
|
|
||||||
# Sets the new remote
|
|
||||||
git_remote=$(git remote)
|
|
||||||
if [ "$git_remote" = "" ]; then # git remote not defined
|
|
||||||
|
|
||||||
if [ "$GIT_TOKEN" = "" ]; then
|
|
||||||
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
|
|
||||||
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
|
|
||||||
else
|
|
||||||
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
git pull origin master
|
|
||||||
|
|
||||||
# Pushes (Forces) the changes in the local repository up to the remote repository
|
|
||||||
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
|
|
||||||
git push origin master 2>&1 | grep -v 'To https'
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
*.lo
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
*.dll
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
*.la
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* HttpContent.h
|
|
||||||
*
|
|
||||||
* This class represents a single item of a multipart-formdata request.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_HttpContent_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_HttpContent_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} HttpContent
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HttpContent();
|
|
||||||
virtual ~HttpContent();
|
|
||||||
|
|
||||||
virtual utility::string_t getContentDisposition() const;
|
|
||||||
virtual void setContentDisposition( const utility::string_t& value );
|
|
||||||
|
|
||||||
virtual utility::string_t getName() const;
|
|
||||||
virtual void setName( const utility::string_t& value );
|
|
||||||
|
|
||||||
virtual utility::string_t getFileName() const;
|
|
||||||
virtual void setFileName( const utility::string_t& value );
|
|
||||||
|
|
||||||
virtual utility::string_t getContentType() const;
|
|
||||||
virtual void setContentType( const utility::string_t& value );
|
|
||||||
|
|
||||||
virtual std::shared_ptr<std::istream> getData() const;
|
|
||||||
virtual void setData( std::shared_ptr<std::istream> value );
|
|
||||||
|
|
||||||
virtual void writeTo( std::ostream& stream );
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// NOTE: no utility::string_t here because those strings can only contain ascii
|
|
||||||
utility::string_t m_ContentDisposition;
|
|
||||||
utility::string_t m_Name;
|
|
||||||
utility::string_t m_FileName;
|
|
||||||
utility::string_t m_ContentType;
|
|
||||||
std::shared_ptr<std::istream> m_Data;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_HttpContent_H_ */
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/HttpContent.h"
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
HttpContent::HttpContent()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpContent::~HttpContent()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t HttpContent::getContentDisposition() const
|
|
||||||
{
|
|
||||||
return m_ContentDisposition;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpContent::setContentDisposition( const utility::string_t & value )
|
|
||||||
{
|
|
||||||
m_ContentDisposition = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t HttpContent::getName() const
|
|
||||||
{
|
|
||||||
return m_Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpContent::setName( const utility::string_t & value )
|
|
||||||
{
|
|
||||||
m_Name = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t HttpContent::getFileName() const
|
|
||||||
{
|
|
||||||
return m_FileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpContent::setFileName( const utility::string_t & value )
|
|
||||||
{
|
|
||||||
m_FileName = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t HttpContent::getContentType() const
|
|
||||||
{
|
|
||||||
return m_ContentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpContent::setContentType( const utility::string_t & value )
|
|
||||||
{
|
|
||||||
m_ContentType = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<std::istream> HttpContent::getData() const
|
|
||||||
{
|
|
||||||
return m_Data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpContent::setData( std::shared_ptr<std::istream> value )
|
|
||||||
{
|
|
||||||
m_Data = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpContent::writeTo( std::ostream& stream )
|
|
||||||
{
|
|
||||||
m_Data->seekg( 0, m_Data->beg );
|
|
||||||
stream << m_Data->rdbuf();
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* IHttpBody.h
|
|
||||||
*
|
|
||||||
* This is the interface for contents that can be sent to a remote HTTP server.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_IHttpBody_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_IHttpBody_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} IHttpBody
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~IHttpBody() { }
|
|
||||||
|
|
||||||
virtual void writeTo( std::ostream& stream ) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_IHttpBody_H_ */
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* JsonBody.h
|
|
||||||
*
|
|
||||||
* This is a JSON http body which can be submitted via http
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_JsonBody_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_JsonBody_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include "{{packageName}}/IHttpBody.h"
|
|
||||||
|
|
||||||
#include <cpprest/json.h>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} JsonBody
|
|
||||||
: public IHttpBody
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
JsonBody( const web::json::value& value );
|
|
||||||
virtual ~JsonBody();
|
|
||||||
|
|
||||||
void writeTo( std::ostream& target ) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
web::json::value m_Json;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_JsonBody_H_ */
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/JsonBody.h"
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
JsonBody::JsonBody( const web::json::value& json)
|
|
||||||
: m_Json(json)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonBody::~JsonBody()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void JsonBody::writeTo( std::ostream& target )
|
|
||||||
{
|
|
||||||
m_Json.serialize(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* {{{appName}}}
|
|
||||||
* {{{appDescription}}}
|
|
||||||
*
|
|
||||||
{{#version}}
|
|
||||||
* The version of the OpenAPI document: {{{.}}}
|
|
||||||
{{/version}}
|
|
||||||
{{#infoEmail}}
|
|
||||||
* Contact: {{{.}}}
|
|
||||||
{{/infoEmail}}
|
|
||||||
*
|
|
||||||
* NOTE: This class is auto generated by OpenAPI-Generator {{{generatorVersion}}}.
|
|
||||||
* https://openapi-generator.tech
|
|
||||||
* Do not edit the class manually.
|
|
||||||
*/
|
|
||||||
|
|
@ -1,288 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#models}}{{#model}}/*
|
|
||||||
* {{classname}}.h
|
|
||||||
*
|
|
||||||
* {{description}}
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_{{classname}}_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_{{classname}}_H_
|
|
||||||
|
|
||||||
{{#hasEnums}}
|
|
||||||
#include <stdexcept>
|
|
||||||
{{/hasEnums}}
|
|
||||||
{{#oneOf}}
|
|
||||||
{{#-first}}
|
|
||||||
#include <variant>
|
|
||||||
{{/-first}}
|
|
||||||
{{/oneOf}}
|
|
||||||
{{^parent}}
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include "{{packageName}}/ModelBase.h"
|
|
||||||
{{/parent}}
|
|
||||||
|
|
||||||
{{#imports}}{{{this}}}
|
|
||||||
{{/imports}}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
{{#vendorExtensions.x-has-forward-declarations}}
|
|
||||||
{{#vendorExtensions.x-forward-declarations}}{{.}}
|
|
||||||
{{/vendorExtensions.x-forward-declarations}}
|
|
||||||
{{/vendorExtensions.x-has-forward-declarations}}
|
|
||||||
{{#oneOf}}{{#-first}}
|
|
||||||
|
|
||||||
class {{declspec}} {{classname}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
{{classname}}() = default;
|
|
||||||
~{{classname}}() = default;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
|
|
||||||
void validate();
|
|
||||||
|
|
||||||
web::json::value toJson() const;
|
|
||||||
|
|
||||||
template<typename Target>
|
|
||||||
bool fromJson(const web::json::value& json) {
|
|
||||||
// convert json to Target type
|
|
||||||
Target target;
|
|
||||||
if (!target.fromJson(json)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_variantValue = target;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) const;
|
|
||||||
|
|
||||||
template<typename Target>
|
|
||||||
bool fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) {
|
|
||||||
// convert multipart to Target type
|
|
||||||
Target target;
|
|
||||||
if (!target.fromMultiPart(multipart, namePrefix)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_variantValue = target;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// {{classname}} members
|
|
||||||
|
|
||||||
using VariantType = std::variant<{{#oneOf}}{{^-first}}, {{/-first}}{{{.}}}{{/oneOf}}>;
|
|
||||||
|
|
||||||
const VariantType& getVariant() const;
|
|
||||||
void setVariant(VariantType value);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
VariantType m_variantValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{/-first}}{{/oneOf}}
|
|
||||||
{{^oneOf}}
|
|
||||||
{{#isEnum}}
|
|
||||||
class {{declspec}} {{classname}}
|
|
||||||
: public {{{parent}}}{{^parent}}ModelBase{{/parent}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
{{classname}}();
|
|
||||||
{{classname}}(utility::string_t str);
|
|
||||||
operator utility::string_t() const {
|
|
||||||
return enumToStrMap.at(getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
{{! operator std::string() const {
|
|
||||||
return enumToStrMap.at(getValue());
|
|
||||||
} }}
|
|
||||||
|
|
||||||
virtual ~{{classname}}();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// ModelBase overrides
|
|
||||||
|
|
||||||
void validate() override;
|
|
||||||
|
|
||||||
web::json::value toJson() const override;
|
|
||||||
bool fromJson(const web::json::value& json) override;
|
|
||||||
|
|
||||||
void toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) const override;
|
|
||||||
bool fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) override;
|
|
||||||
|
|
||||||
enum class e{{classname}}
|
|
||||||
{
|
|
||||||
{{#allowableValues}}
|
|
||||||
{{#enumVars}}
|
|
||||||
{{#enumDescription}}
|
|
||||||
/// <summary>
|
|
||||||
/// {{.}}
|
|
||||||
/// </summary>
|
|
||||||
{{/enumDescription}}
|
|
||||||
{{{name}}}{{^last}},{{/last}}
|
|
||||||
{{/enumVars}}
|
|
||||||
{{/allowableValues}}
|
|
||||||
};
|
|
||||||
|
|
||||||
e{{classname}} getValue() const;
|
|
||||||
void setValue(e{{classname}} const value);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
e{{classname}} m_value;
|
|
||||||
std::map<e{{classname}},utility::string_t> enumToStrMap = {
|
|
||||||
{{#allowableValues}}
|
|
||||||
{{#enumVars}}
|
|
||||||
{ e{{classname}}::{{{name}}}, _XPLATSTR("{{{name}}}") }{{^-last}},{{/-last}}
|
|
||||||
{{/enumVars}}
|
|
||||||
{{/allowableValues}}
|
|
||||||
};
|
|
||||||
std::map<utility::string_t,e{{classname}}> strToEnumMap = {
|
|
||||||
{{#allowableValues}}
|
|
||||||
{{#enumVars}}
|
|
||||||
{ _XPLATSTR("{{{name}}}"), e{{classname}}::{{{name}}} }{{^-last}},{{/-last}}
|
|
||||||
{{/enumVars}}
|
|
||||||
{{/allowableValues}}
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
{{/isEnum}}
|
|
||||||
{{^isEnum}}
|
|
||||||
|
|
||||||
{{#description}}
|
|
||||||
/// <summary>
|
|
||||||
/// {{description}}
|
|
||||||
/// </summary>
|
|
||||||
{{/description}}
|
|
||||||
class {{declspec}} {{classname}}
|
|
||||||
: public {{{parent}}}{{^parent}}ModelBase{{/parent}}
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
{{classname}}();
|
|
||||||
virtual ~{{classname}}();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// ModelBase overrides
|
|
||||||
|
|
||||||
void validate() override;
|
|
||||||
|
|
||||||
web::json::value toJson() const override;
|
|
||||||
bool fromJson(const web::json::value& json) override;
|
|
||||||
|
|
||||||
void toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) const override;
|
|
||||||
bool fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) override;
|
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// {{classname}} members
|
|
||||||
|
|
||||||
{{! ENUM DEFINITIONS }}
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{#isEnum}}
|
|
||||||
enum class {{#isContainer}}{{{enumName}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}
|
|
||||||
{
|
|
||||||
{{#allowableValues}}
|
|
||||||
{{#enumVars}}
|
|
||||||
{{{name}}}{{^last}},{{/last}}
|
|
||||||
{{/enumVars}}
|
|
||||||
{{/allowableValues}}
|
|
||||||
};
|
|
||||||
{{#description}}
|
|
||||||
/// <summary>
|
|
||||||
/// {{description}}
|
|
||||||
/// </summary>
|
|
||||||
{{/description}}
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isInherited}}
|
|
||||||
{{/vars}}
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{#isContainer}}
|
|
||||||
{{! ENUM CONVERSIONS }}
|
|
||||||
{{{enumName}}} to{{{enumName}}}(const utility::string_t& value) const;
|
|
||||||
const utility::string_t from{{{enumName}}}(const {{{enumName}}} value) const;
|
|
||||||
{{#isArray}}
|
|
||||||
{{{datatypeWithEnum}}} to{{{enumName}}}(const {{{dataType}}}& value) const;
|
|
||||||
{{{dataType}}} from{{{enumName}}}(const {{{datatypeWithEnum}}}& value) const;
|
|
||||||
{{/isArray}}{{/isContainer}}{{^isContainer}}
|
|
||||||
{{{datatypeWithEnum}}} to{{{datatypeWithEnum}}}(const utility::string_t& value) const;
|
|
||||||
const utility::string_t from{{{datatypeWithEnum}}}(const {{{datatypeWithEnum}}} value) const;
|
|
||||||
{{/isContainer}}
|
|
||||||
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isInherited}}
|
|
||||||
{{/vars}}
|
|
||||||
|
|
||||||
{{! SETTER AND GETTERS }}
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{#description}}
|
|
||||||
/// <summary>
|
|
||||||
/// {{description}}
|
|
||||||
/// </summary>
|
|
||||||
{{/description}}
|
|
||||||
{{#isContainer}}
|
|
||||||
{{^isEnum}}
|
|
||||||
{{{dataType}}} {{getter}}() const;
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{^isContainer}}
|
|
||||||
{{^isEnum}}
|
|
||||||
{{{dataType}}} {{getter}}() const;
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{^isMap}}
|
|
||||||
{{{datatypeWithEnum}}} {{getter}}() const;
|
|
||||||
{{/isMap}}
|
|
||||||
{{#isMap}}
|
|
||||||
{{{dataType}}} {{getter}}() const;
|
|
||||||
{{/isMap}}
|
|
||||||
{{/isEnum}}
|
|
||||||
bool {{nameInCamelCase}}IsSet() const;
|
|
||||||
void unset{{name}}();
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
void {{setter}}({{{dataType}}} value);
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{^isEnum}}
|
|
||||||
void {{setter}}(const {{{dataType}}}& value);
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{#isEnum}}
|
|
||||||
void {{setter}}(const {{^isMap}}{{{datatypeWithEnum}}}{{/isMap}}{{#isMap}}{{{dataType}}}{{/isMap}} value);
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isInherited}}
|
|
||||||
|
|
||||||
{{/vars}}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{^isEnum}}
|
|
||||||
{{{dataType}}} m_{{name}};
|
|
||||||
{{/isEnum}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{^isMap}}{{{datatypeWithEnum}}}{{/isMap}}{{#isMap}}{{{dataType}}}{{/isMap}} m_{{name}};
|
|
||||||
{{/isEnum}}
|
|
||||||
bool m_{{name}}IsSet;
|
|
||||||
{{/isInherited}}
|
|
||||||
|
|
||||||
{{/vars}}
|
|
||||||
};
|
|
||||||
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/oneOf}}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_{{classname}}_H_ */
|
|
||||||
{{/model}}
|
|
||||||
{{/models}}
|
|
||||||
|
|
@ -1,457 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
{{#models}}{{#model}}
|
|
||||||
|
|
||||||
#include "{{packageName}}/model/{{classFilename}}.h"
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
{{#oneOf}}
|
|
||||||
{{#-first}}
|
|
||||||
|
|
||||||
void {{classname}}::validate()
|
|
||||||
{
|
|
||||||
// TODO: implement validation
|
|
||||||
}
|
|
||||||
|
|
||||||
const {{classname}}::VariantType& {{classname}}::getVariant() const
|
|
||||||
{
|
|
||||||
return m_variantValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::setVariant({{classname}}::VariantType value)
|
|
||||||
{
|
|
||||||
m_variantValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
web::json::value {{classname}}::toJson() const
|
|
||||||
{
|
|
||||||
web::json::value val = web::json::value::object();
|
|
||||||
|
|
||||||
std::visit([&](auto&& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, std::monostate>) {
|
|
||||||
val = web::json::value::null();
|
|
||||||
} else {
|
|
||||||
val = arg.toJson();
|
|
||||||
}
|
|
||||||
}, m_variantValue);
|
|
||||||
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix) const
|
|
||||||
{
|
|
||||||
std::visit([&](auto&& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (!std::is_same_v<T, std::monostate>) {
|
|
||||||
arg.toMultipart(multipart, prefix);
|
|
||||||
}
|
|
||||||
}, m_variantValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#oneOf}}
|
|
||||||
template bool {{classname}}::fromJson<{{.}}>(const web::json::value& json);
|
|
||||||
template bool {{classname}}::fromMultiPart<{{.}}>(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix);
|
|
||||||
{{/oneOf}}
|
|
||||||
|
|
||||||
{{/-first}}
|
|
||||||
{{/oneOf}}
|
|
||||||
{{^oneOf}}
|
|
||||||
{{#isEnum}}
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
using EnumUnderlyingType = {{#isNumeric}}int64_t{{/isNumeric}}{{^isNumeric}}utility::string_t{{/isNumeric}};
|
|
||||||
|
|
||||||
{{classname}}::e{{classname}} toEnum(const EnumUnderlyingType& val)
|
|
||||||
{
|
|
||||||
{{#allowableValues}}
|
|
||||||
{{#isNumeric}}
|
|
||||||
switch (val)
|
|
||||||
{
|
|
||||||
{{#enumVars}}
|
|
||||||
case {{value}}:
|
|
||||||
return {{classname}}::e{{classname}}::{{name}};
|
|
||||||
{{#-last}}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
{{/-last}}
|
|
||||||
{{/enumVars}}
|
|
||||||
}
|
|
||||||
{{/isNumeric}}
|
|
||||||
{{^isNumeric}}
|
|
||||||
{{#enumVars}}
|
|
||||||
if (val == utility::conversions::to_string_t(_XPLATSTR("{{{value}}}")))
|
|
||||||
return {{classname}}::e{{classname}}::{{name}};
|
|
||||||
{{/enumVars}}
|
|
||||||
{{/isNumeric}}
|
|
||||||
{{/allowableValues}}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
EnumUnderlyingType fromEnum({{classname}}::e{{classname}} e)
|
|
||||||
{
|
|
||||||
{{#allowableValues}}
|
|
||||||
switch (e)
|
|
||||||
{
|
|
||||||
{{#enumVars}}
|
|
||||||
case {{classname}}::e{{classname}}::{{name}}:
|
|
||||||
return {{#isNumeric}}{{value}}{{/isNumeric}}{{^isNumeric}}_XPLATSTR("{{value}}"){{/isNumeric}};
|
|
||||||
{{#-last}}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
{{/-last}}
|
|
||||||
{{/enumVars}}
|
|
||||||
}
|
|
||||||
{{/allowableValues}}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::{{classname}}()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::~{{classname}}()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::validate()
|
|
||||||
{
|
|
||||||
// TODO: implement validation
|
|
||||||
}
|
|
||||||
|
|
||||||
web::json::value {{classname}}::toJson() const
|
|
||||||
{
|
|
||||||
auto val = fromEnum(m_value);
|
|
||||||
return web::json::value(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool {{classname}}::fromJson(const web::json::value& val)
|
|
||||||
{
|
|
||||||
m_value = toEnum({{#isNumeric}}val.as_number().to_int64(){{/isNumeric}}{{^isNumeric}}val.as_string(){{/isNumeric}});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix) const
|
|
||||||
{
|
|
||||||
utility::string_t namePrefix = prefix;
|
|
||||||
if (!namePrefix.empty() && namePrefix.back() != _XPLATSTR('.'))
|
|
||||||
{
|
|
||||||
namePrefix.push_back(_XPLATSTR('.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto e = fromEnum(m_value);
|
|
||||||
multipart->add(ModelBase::toHttpContent(namePrefix, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool {{classname}}::fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix)
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
utility::string_t namePrefix = prefix;
|
|
||||||
if (!namePrefix.empty() && namePrefix.back() != _XPLATSTR('.'))
|
|
||||||
{
|
|
||||||
namePrefix.push_back(_XPLATSTR('.'));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
EnumUnderlyingType e;
|
|
||||||
ok = ModelBase::fromHttpContent(multipart->getContent(namePrefix), e);
|
|
||||||
if (ok)
|
|
||||||
{
|
|
||||||
auto v = toEnum(e);
|
|
||||||
setValue(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::e{{classname}} {{classname}}::getValue() const
|
|
||||||
{
|
|
||||||
return m_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::setValue({{classname}}::e{{classname}} const value)
|
|
||||||
{
|
|
||||||
m_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::{{classname}}(utility::string_t str){
|
|
||||||
setValue( strToEnumMap[str] );
|
|
||||||
}
|
|
||||||
|
|
||||||
{{/isEnum}}
|
|
||||||
{{^isEnum}}
|
|
||||||
|
|
||||||
{{classname}}::{{classname}}()
|
|
||||||
{
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{^isContainer}}
|
|
||||||
{{^isEnum}}
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
m_{{name}} = {{{defaultValue}}};
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{^isPrimitiveType}}
|
|
||||||
{{#isString}}
|
|
||||||
m_{{name}} = {{{defaultValue}}};
|
|
||||||
{{/isString}}
|
|
||||||
{{#isDateTime}}
|
|
||||||
m_{{name}} = {{{defaultValue}}};
|
|
||||||
{{/isDateTime}}
|
|
||||||
{{/isPrimitiveType}}
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isContainer}}
|
|
||||||
m_{{name}}IsSet = false;
|
|
||||||
{{/isInherited}}
|
|
||||||
{{/vars}}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{classname}}::~{{classname}}()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::validate()
|
|
||||||
{
|
|
||||||
// TODO: implement validation
|
|
||||||
}
|
|
||||||
|
|
||||||
web::json::value {{classname}}::toJson() const
|
|
||||||
{
|
|
||||||
{{#parent}}
|
|
||||||
web::json::value val = this->{{{.}}}::toJson();
|
|
||||||
{{/parent}}
|
|
||||||
{{^parent}}
|
|
||||||
web::json::value val = web::json::value::object();
|
|
||||||
{{/parent}}
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
if(m_{{name}}IsSet)
|
|
||||||
{
|
|
||||||
{{#isEnum}}{{#isContainer}}{{#isArray}}
|
|
||||||
{{{dataType}}} refVal = from{{{enumName}}}(m_{{name}});
|
|
||||||
{{/isArray}}{{#isMap}}
|
|
||||||
val[utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))] = ModelBase::toJson(m_{{name}});
|
|
||||||
{{/isMap}}{{/isContainer}}{{^isContainer}}
|
|
||||||
utility::string_t refVal = from{{{datatypeWithEnum}}}(m_{{name}});
|
|
||||||
{{/isContainer}}{{^isMap}}val[utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))] = ModelBase::toJson(refVal);
|
|
||||||
{{/isMap}}{{/isEnum}}
|
|
||||||
{{^isEnum}}
|
|
||||||
val[utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))] = ModelBase::toJson(m_{{name}});
|
|
||||||
{{/isEnum}}
|
|
||||||
}
|
|
||||||
{{/isInherited}}
|
|
||||||
{{/vars}}
|
|
||||||
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool {{classname}}::fromJson(const web::json::value& val)
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
{{#parent}}
|
|
||||||
ok &= this->{{{.}}}::fromJson(val);
|
|
||||||
{{/parent}}
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
if(val.has_field(utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))))
|
|
||||||
{
|
|
||||||
const web::json::value& fieldValue = val.at(utility::conversions::to_string_t(_XPLATSTR("{{baseName}}")));
|
|
||||||
if(!fieldValue.is_null())
|
|
||||||
{
|
|
||||||
{{{dataType}}} refVal_{{setter}};
|
|
||||||
ok &= ModelBase::fromJson(fieldValue, refVal_{{setter}});
|
|
||||||
{{^isEnum}}
|
|
||||||
{{setter}}(refVal_{{setter}});
|
|
||||||
{{/isEnum}}
|
|
||||||
{{#isEnum}}{{#isContainer}}{{#isArray}}
|
|
||||||
{{setter}}(to{{{enumName}}}(refVal_{{setter}}));
|
|
||||||
{{/isArray}}{{#isMap}}
|
|
||||||
{{setter}}(refVal_{{setter}});
|
|
||||||
{{/isMap}}{{/isContainer}}{{^isContainer}}
|
|
||||||
{{setter}}(to{{{datatypeWithEnum}}}(refVal_{{setter}}));
|
|
||||||
{{/isContainer}}{{/isEnum}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{{/isInherited}}
|
|
||||||
{{/vars}}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix) const
|
|
||||||
{
|
|
||||||
utility::string_t namePrefix = prefix;
|
|
||||||
if(namePrefix.size() > 0 && namePrefix.substr(namePrefix.size() - 1) != utility::conversions::to_string_t(_XPLATSTR(".")))
|
|
||||||
{
|
|
||||||
namePrefix += utility::conversions::to_string_t(_XPLATSTR("."));
|
|
||||||
}
|
|
||||||
{{#vars}}
|
|
||||||
if(m_{{name}}IsSet)
|
|
||||||
{
|
|
||||||
{{^isEnum}}
|
|
||||||
multipart->add(ModelBase::toHttpContent(namePrefix + utility::conversions::to_string_t(_XPLATSTR("{{baseName}}")), m_{{name}}));
|
|
||||||
{{/isEnum}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{#isContainer}}
|
|
||||||
{{#isArray}}
|
|
||||||
multipart->add(ModelBase::toHttpContent(namePrefix + utility::conversions::to_string_t(_XPLATSTR("{{baseName}}")), from{{{enumName}}}(m_{{name}})));
|
|
||||||
{{/isArray}}{{#isMap}}
|
|
||||||
multipart->add(ModelBase::toHttpContent(namePrefix + utility::conversions::to_string_t(_XPLATSTR("{{baseName}}")), m_{{name}}));
|
|
||||||
{{/isMap}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{^isContainer}}
|
|
||||||
multipart->add(ModelBase::toHttpContent(namePrefix + utility::conversions::to_string_t(_XPLATSTR("{{baseName}}")), from{{{datatypeWithEnum}}}(m_{{name}})));
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isEnum}}
|
|
||||||
}
|
|
||||||
{{/vars}}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool {{classname}}::fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix)
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
utility::string_t namePrefix = prefix;
|
|
||||||
if(namePrefix.size() > 0 && namePrefix.substr(namePrefix.size() - 1) != utility::conversions::to_string_t(_XPLATSTR(".")))
|
|
||||||
{
|
|
||||||
namePrefix += utility::conversions::to_string_t(_XPLATSTR("."));
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#vars}}
|
|
||||||
if(multipart->hasContent(utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))))
|
|
||||||
{
|
|
||||||
{{{dataType}}} refVal_{{setter}};
|
|
||||||
ok &= ModelBase::fromHttpContent(multipart->getContent(utility::conversions::to_string_t(_XPLATSTR("{{baseName}}"))), refVal_{{setter}} );
|
|
||||||
{{^isEnum}}
|
|
||||||
{{setter}}(refVal_{{setter}});
|
|
||||||
{{/isEnum}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{#isContainer}}
|
|
||||||
{{#isArray}}
|
|
||||||
{{setter}}(to{{{enumName}}}(refVal_{{setter}}));
|
|
||||||
{{/isArray}}
|
|
||||||
{{#isMap}}
|
|
||||||
{{setter}}(refVal_{{setter}});
|
|
||||||
{{/isMap}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{^isContainer}}
|
|
||||||
{{setter}}(to{{{datatypeWithEnum}}}(refVal_{{setter}}));
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isEnum}}
|
|
||||||
}
|
|
||||||
{{/vars}}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{#isContainer}}
|
|
||||||
{{classname}}::{{{enumName}}} {{classname}}::to{{{enumName}}}(const utility::string_t& value) const
|
|
||||||
{{/isContainer}}
|
|
||||||
{{^isContainer}}
|
|
||||||
{{classname}}::{{{datatypeWithEnum}}} {{classname}}::to{{{datatypeWithEnum}}}(const {{dataType}}& value) const
|
|
||||||
{{/isContainer}}
|
|
||||||
{
|
|
||||||
{{#allowableValues}}{{#enumVars}}
|
|
||||||
if (value == utility::conversions::to_string_t("{{value}}")) {
|
|
||||||
return {{#isContainer}}{{{enumName}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}::{{name}};
|
|
||||||
}
|
|
||||||
{{/enumVars}}{{/allowableValues}}
|
|
||||||
throw std::invalid_argument("Invalid value for conversion to {{{datatypeWithEnum}}}");
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#isContainer}}
|
|
||||||
const utility::string_t {{classname}}::from{{{enumName}}}(const {{{enumName}}} value) const
|
|
||||||
{{/isContainer}}{{^isContainer}}
|
|
||||||
const {{dataType}} {{classname}}::from{{{datatypeWithEnum}}}(const {{{datatypeWithEnum}}} value) const
|
|
||||||
{{/isContainer}}
|
|
||||||
{
|
|
||||||
switch(value)
|
|
||||||
{
|
|
||||||
{{#allowableValues}}{{#enumVars}}
|
|
||||||
case {{#isContainer}}{{{enumName}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}{{/isContainer}}::{{name}}: return utility::conversions::to_string_t("{{value}}");
|
|
||||||
{{/enumVars}}{{/allowableValues}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#isContainer}}
|
|
||||||
{{#isArray}}
|
|
||||||
{{{dataType}}} {{{classname}}}::from{{{enumName}}}(const {{{datatypeWithEnum}}}& value) const
|
|
||||||
{
|
|
||||||
{{{dataType}}} ret;
|
|
||||||
for (auto it = value.begin(); it != value.end(); it++) {
|
|
||||||
ret.push_back(from{{{enumName}}}(*it));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{{baseType}}}<{{classname}}::{{{enumName}}}> {{{classname}}}::to{{{enumName}}}(const {{{dataType}}}& value) const
|
|
||||||
{
|
|
||||||
{{{datatypeWithEnum}}} ret;
|
|
||||||
for (auto it = value.begin(); it != value.end(); it++) {
|
|
||||||
ret.push_back(to{{{enumName}}}(*it));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
{{/isArray}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isInherited}}
|
|
||||||
{{/vars}}
|
|
||||||
|
|
||||||
{{#vars}}
|
|
||||||
{{^isInherited}}
|
|
||||||
{{#isContainer}}
|
|
||||||
{{^isEnum}}
|
|
||||||
{{{dataType}}} {{classname}}::{{getter}}() const
|
|
||||||
{
|
|
||||||
return m_{{name}};
|
|
||||||
}
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{^isContainer}}
|
|
||||||
{{^isEnum}}
|
|
||||||
{{{dataType}}} {{classname}}::{{getter}}() const
|
|
||||||
{
|
|
||||||
return m_{{name}};
|
|
||||||
}
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/isContainer}}
|
|
||||||
{{#isEnum}}
|
|
||||||
{{^isMap}}{{#isArray}}{{{baseType}}}<{{/isArray}}{{{classname}}}::{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isMap}}{{#isMap}}{{{dataType}}}{{/isMap}} {{classname}}::{{getter}}() const
|
|
||||||
{
|
|
||||||
return m_{{name}};
|
|
||||||
}
|
|
||||||
{{/isEnum}}
|
|
||||||
|
|
||||||
{{#isPrimitiveType}}
|
|
||||||
void {{classname}}::{{setter}}({{{dataType}}} value)
|
|
||||||
{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isEnum}}
|
|
||||||
void {{classname}}::{{setter}}(const {{{dataType}}}& value)
|
|
||||||
{{/isEnum}}{{/isPrimitiveType}}{{#isEnum}}
|
|
||||||
void {{classname}}::{{setter}}(const {{^isMap}}{{{datatypeWithEnum}}}{{/isMap}}{{#isMap}}{{{dataType}}}{{/isMap}} value)
|
|
||||||
{{/isEnum}}
|
|
||||||
{
|
|
||||||
m_{{name}} = value;
|
|
||||||
m_{{name}}IsSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool {{classname}}::{{nameInCamelCase}}IsSet() const
|
|
||||||
{
|
|
||||||
return m_{{name}}IsSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
void {{classname}}::unset{{name}}()
|
|
||||||
{
|
|
||||||
m_{{name}}IsSet = false;
|
|
||||||
}
|
|
||||||
{{/isInherited}}{{/vars}}
|
|
||||||
{{/isEnum}}
|
|
||||||
{{/oneOf}}
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
|
|
||||||
{{/model}}
|
|
||||||
{{/models}}
|
|
||||||
|
|
@ -1,493 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* ModelBase.h
|
|
||||||
*
|
|
||||||
* This is the base class for all model classes
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_ModelBase_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_ModelBase_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
|
|
||||||
#include "{{packageName}}/HttpContent.h"
|
|
||||||
#include "{{packageName}}/MultipartFormData.h"
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
#include <cpprest/json.h>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} ModelBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ModelBase();
|
|
||||||
virtual ~ModelBase();
|
|
||||||
|
|
||||||
virtual void validate() = 0;
|
|
||||||
|
|
||||||
virtual web::json::value toJson() const = 0;
|
|
||||||
virtual bool fromJson( const web::json::value& json ) = 0;
|
|
||||||
|
|
||||||
virtual void toMultipart( std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix ) const = 0;
|
|
||||||
virtual bool fromMultiPart( std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix ) = 0;
|
|
||||||
|
|
||||||
virtual bool isSet() const;
|
|
||||||
|
|
||||||
static utility::string_t toString( const bool val );
|
|
||||||
static utility::string_t toString( const float val );
|
|
||||||
static utility::string_t toString( const double val );
|
|
||||||
static utility::string_t toString( const int32_t val );
|
|
||||||
static utility::string_t toString( const int64_t val );
|
|
||||||
static utility::string_t toString( const utility::string_t &val );
|
|
||||||
static utility::string_t toString( const utility::datetime &val );
|
|
||||||
static utility::string_t toString( const web::json::value &val );
|
|
||||||
static utility::string_t toString( const std::shared_ptr<HttpContent>& val );
|
|
||||||
template <typename T>
|
|
||||||
static utility::string_t toString( const std::shared_ptr<T>& val );
|
|
||||||
template <typename T>
|
|
||||||
static utility::string_t toString( const std::vector<T> & val );
|
|
||||||
template <typename T>
|
|
||||||
static utility::string_t toString( const std::set<T> & val );
|
|
||||||
|
|
||||||
static web::json::value toJson( bool val );
|
|
||||||
static web::json::value toJson( float val );
|
|
||||||
static web::json::value toJson( double val );
|
|
||||||
static web::json::value toJson( int32_t val );
|
|
||||||
static web::json::value toJson( int64_t val );
|
|
||||||
static web::json::value toJson( const utility::string_t& val );
|
|
||||||
static web::json::value toJson( const utility::datetime& val );
|
|
||||||
static web::json::value toJson( const web::json::value& val );
|
|
||||||
static web::json::value toJson( const std::shared_ptr<HttpContent>& val );
|
|
||||||
template<typename T>
|
|
||||||
static web::json::value toJson( const std::shared_ptr<T>& val );
|
|
||||||
static web::json::value toJson( const std::shared_ptr<utility::datetime>& val );
|
|
||||||
template<typename T>
|
|
||||||
static web::json::value toJson( const std::vector<T>& val );
|
|
||||||
template<typename T>
|
|
||||||
static web::json::value toJson( const std::set<T>& val );
|
|
||||||
template<typename T>
|
|
||||||
static web::json::value toJson( const std::map<utility::string_t, T>& val );
|
|
||||||
|
|
||||||
static bool fromString( const utility::string_t& val, bool & );
|
|
||||||
static bool fromString( const utility::string_t& val, float & );
|
|
||||||
static bool fromString( const utility::string_t& val, double & );
|
|
||||||
static bool fromString( const utility::string_t& val, int32_t & );
|
|
||||||
static bool fromString( const utility::string_t& val, int64_t & );
|
|
||||||
static bool fromString( const utility::string_t& val, utility::string_t & );
|
|
||||||
static bool fromString( const utility::string_t& val, utility::datetime & );
|
|
||||||
static bool fromString( const utility::string_t& val, web::json::value & );
|
|
||||||
static bool fromString( const utility::string_t& val, std::shared_ptr<HttpContent> & );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromString( const utility::string_t& val, std::shared_ptr<T>& );
|
|
||||||
static bool fromString( const utility::string_t& val, std::shared_ptr<utility::datetime>& outVal );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromString( const utility::string_t& val, std::vector<T> & );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromString( const utility::string_t& val, std::set<T> & );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromString( const utility::string_t& val, std::map<utility::string_t, T> & );
|
|
||||||
|
|
||||||
static bool fromJson( const web::json::value& val, bool & );
|
|
||||||
static bool fromJson( const web::json::value& val, float & );
|
|
||||||
static bool fromJson( const web::json::value& val, double & );
|
|
||||||
static bool fromJson( const web::json::value& val, int32_t & );
|
|
||||||
static bool fromJson( const web::json::value& val, int64_t & );
|
|
||||||
static bool fromJson( const web::json::value& val, utility::string_t & );
|
|
||||||
static bool fromJson( const web::json::value& val, utility::datetime & );
|
|
||||||
static bool fromJson( const web::json::value& val, web::json::value & );
|
|
||||||
static bool fromJson( const web::json::value& val, std::shared_ptr<HttpContent> & );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromJson( const web::json::value& val, std::shared_ptr<T>& );
|
|
||||||
static bool fromJson( const web::json::value& val, std::shared_ptr<utility::datetime> &outVal );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromJson( const web::json::value& val, std::vector<T> & );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromJson( const web::json::value& val, std::set<T> & );
|
|
||||||
template<typename T>
|
|
||||||
static bool fromJson( const web::json::value& val, std::map<utility::string_t, T> & );
|
|
||||||
|
|
||||||
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, bool value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, float value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, double value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, int32_t value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, int64_t value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const utility::string_t& value, const utility::string_t& contentType = utility::conversions::to_string_t(""));
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const utility::datetime& value, const utility::string_t& contentType = utility::conversions::to_string_t(""));
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const web::json::value& value, const utility::string_t& contentType = utility::conversions::to_string_t("application/json") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const std::shared_ptr<HttpContent>& );
|
|
||||||
template <typename T>
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const std::shared_ptr<T>& , const utility::string_t& contentType = utility::conversions::to_string_t("application/json") );
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent(const utility::string_t& name, const std::shared_ptr<utility::datetime>& value , const utility::string_t& contentType = utility::conversions::to_string_t("application/json") );
|
|
||||||
template <typename T>
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const std::vector<T>& value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
template <typename T>
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const std::set<T>& value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
template <typename T>
|
|
||||||
static std::shared_ptr<HttpContent> toHttpContent( const utility::string_t& name, const std::map<utility::string_t, T>& value, const utility::string_t& contentType = utility::conversions::to_string_t("") );
|
|
||||||
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, bool & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, float & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, double & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, int64_t & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, int32_t & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, utility::string_t & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, utility::datetime & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, web::json::value & );
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, std::shared_ptr<HttpContent>& );
|
|
||||||
template <typename T>
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, std::shared_ptr<T>& );
|
|
||||||
template <typename T>
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, std::vector<T> & );
|
|
||||||
template <typename T>
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, std::set<T> & );
|
|
||||||
template <typename T>
|
|
||||||
static bool fromHttpContent( std::shared_ptr<HttpContent> val, std::map<utility::string_t, T> & );
|
|
||||||
|
|
||||||
static utility::string_t toBase64( utility::string_t value );
|
|
||||||
static utility::string_t toBase64( std::shared_ptr<std::istream> value );
|
|
||||||
static std::shared_ptr<std::istream> fromBase64( const utility::string_t& encoded );
|
|
||||||
protected:
|
|
||||||
bool m_IsSet;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
utility::string_t ModelBase::toString( const std::shared_ptr<T>& val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
if( val != nullptr )
|
|
||||||
{
|
|
||||||
val->toJson().serialize(ss);
|
|
||||||
}
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::vector to string
|
|
||||||
template<typename T>
|
|
||||||
utility::string_t ModelBase::toString( const std::vector<T> & val )
|
|
||||||
{
|
|
||||||
utility::string_t strArray;
|
|
||||||
for ( const auto &item : val )
|
|
||||||
{
|
|
||||||
strArray.append( toString(item) + "," );
|
|
||||||
}
|
|
||||||
if (val.count() > 0)
|
|
||||||
{
|
|
||||||
strArray.pop_back();
|
|
||||||
}
|
|
||||||
return strArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::set to string
|
|
||||||
template<typename T>
|
|
||||||
utility::string_t ModelBase::toString( const std::set<T> & val )
|
|
||||||
{
|
|
||||||
utility::string_t strArray;
|
|
||||||
for ( const auto &item : val )
|
|
||||||
{
|
|
||||||
strArray.append( toString(item) + "," );
|
|
||||||
}
|
|
||||||
if (val.count() > 0)
|
|
||||||
{
|
|
||||||
strArray.pop_back();
|
|
||||||
}
|
|
||||||
return strArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
web::json::value ModelBase::toJson( const std::shared_ptr<T>& val )
|
|
||||||
{
|
|
||||||
web::json::value retVal;
|
|
||||||
if(val != nullptr)
|
|
||||||
{
|
|
||||||
retVal = val->toJson();
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::vector to json
|
|
||||||
template<typename T>
|
|
||||||
web::json::value ModelBase::toJson( const std::vector<T>& value )
|
|
||||||
{
|
|
||||||
std::vector<web::json::value> ret;
|
|
||||||
for ( const auto& x : value )
|
|
||||||
{
|
|
||||||
ret.push_back( toJson(x) );
|
|
||||||
}
|
|
||||||
return web::json::value::array(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::set to json
|
|
||||||
template<typename T>
|
|
||||||
web::json::value ModelBase::toJson( const std::set<T>& value )
|
|
||||||
{
|
|
||||||
// There's no prototype web::json::value::array(...) taking a std::set parameter. Converting to std::vector to get an array.
|
|
||||||
std::vector<web::json::value> ret;
|
|
||||||
for ( const auto& x : value )
|
|
||||||
{
|
|
||||||
ret.push_back( toJson(x) );
|
|
||||||
}
|
|
||||||
return web::json::value::array(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
web::json::value ModelBase::toJson( const std::map<utility::string_t, T>& val )
|
|
||||||
{
|
|
||||||
web::json::value obj;
|
|
||||||
for ( const auto &itemkey : val )
|
|
||||||
{
|
|
||||||
obj[itemkey.first] = toJson( itemkey.second );
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, std::shared_ptr<T>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
if(outVal == nullptr)
|
|
||||||
{
|
|
||||||
outVal = std::make_shared<T>();
|
|
||||||
}
|
|
||||||
if( outVal != nullptr )
|
|
||||||
{
|
|
||||||
ok = outVal->fromJson(web::json::value::parse(val));
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromString(const utility::string_t& val, std::vector<T>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
web::json::value jsonValue = web::json::value::parse(val);
|
|
||||||
if (jsonValue.is_array())
|
|
||||||
{
|
|
||||||
for (const web::json::value& jitem : jsonValue.as_array())
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok &= fromJson(jitem, item);
|
|
||||||
outVal.push_back(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok = fromJson(jsonValue, item);
|
|
||||||
outVal.push_back(item);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromString(const utility::string_t& val, std::set<T>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
web::json::value jsonValue = web::json::value::parse(val);
|
|
||||||
if (jsonValue.is_array())
|
|
||||||
{
|
|
||||||
for (const web::json::value& jitem : jsonValue.as_array())
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok &= fromJson(jitem, item);
|
|
||||||
outVal.insert(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok = fromJson(jsonValue, item);
|
|
||||||
outVal.insert(item);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromString(const utility::string_t& val, std::map<utility::string_t, T>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
web::json::value jsonValue = web::json::value::parse(val);
|
|
||||||
if (jsonValue.is_array())
|
|
||||||
{
|
|
||||||
for (const web::json::value& jitem : jsonValue.as_array())
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok &= fromJson(jitem, item);
|
|
||||||
outVal.insert({ val, item });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok = fromJson(jsonValue, item);
|
|
||||||
outVal.insert({ val, item });
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, std::shared_ptr<T> &outVal )
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
if(outVal == nullptr)
|
|
||||||
{
|
|
||||||
outVal = std::make_shared<T>();
|
|
||||||
}
|
|
||||||
if( outVal != nullptr )
|
|
||||||
{
|
|
||||||
ok = outVal->fromJson(val);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, std::vector<T> &outVal )
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
if (val.is_array())
|
|
||||||
{
|
|
||||||
for (const web::json::value & jitem : val.as_array())
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok &= fromJson(jitem, item);
|
|
||||||
outVal.push_back(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromJson(const web::json::value& val, std::set<T>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
if (val.is_array())
|
|
||||||
{
|
|
||||||
for (const web::json::value& jitem : val.as_array())
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok &= fromJson(jitem, item);
|
|
||||||
outVal.insert(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
T item;
|
|
||||||
ok = fromJson(val, item);
|
|
||||||
outVal.insert(item);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template<typename T>
|
|
||||||
bool ModelBase::fromJson( const web::json::value& jval, std::map<utility::string_t, T> &outVal )
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
if ( jval.is_object() )
|
|
||||||
{
|
|
||||||
auto obj = jval.as_object();
|
|
||||||
for( auto objItr = obj.begin() ; objItr != obj.end() ; objItr++ )
|
|
||||||
{
|
|
||||||
T itemVal;
|
|
||||||
ok &= fromJson(objItr->second, itemVal);
|
|
||||||
outVal.insert(std::pair<utility::string_t, T>(objItr->first, itemVal));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent(const utility::string_t& name, const std::shared_ptr<T>& value , const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content = std::make_shared<HttpContent>();
|
|
||||||
if (value != nullptr )
|
|
||||||
{
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::make_shared<std::stringstream>( utility::conversions::to_utf8string(value->toJson().serialize()) ) );
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const std::vector<T>& value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
web::json::value json_array = ModelBase::toJson(value);
|
|
||||||
std::shared_ptr<HttpContent> content = std::make_shared<HttpContent>();
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::make_shared<std::stringstream>( utility::conversions::to_utf8string(json_array.serialize()) ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const std::set<T>& value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
web::json::value json_array = ModelBase::toJson(value);
|
|
||||||
std::shared_ptr<HttpContent> content = std::make_shared<HttpContent>();
|
|
||||||
content->setName(name);
|
|
||||||
content->setContentDisposition(utility::conversions::to_string_t("form-data"));
|
|
||||||
content->setContentType(contentType);
|
|
||||||
content->setData( std::make_shared<std::stringstream>( utility::conversions::to_utf8string(json_array.serialize()) ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const std::map<utility::string_t, T>& value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
web::json::value jobj = ModelBase::toJson(value);
|
|
||||||
std::shared_ptr<HttpContent> content = std::make_shared<HttpContent>();
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::make_shared<std::stringstream>( utility::conversions::to_utf8string(jobj.serialize()) ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
bool ModelBase::fromHttpContent( std::shared_ptr<HttpContent> val, std::shared_ptr<T>& outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if(val == nullptr) return false;
|
|
||||||
if( outVal == nullptr )
|
|
||||||
{
|
|
||||||
outVal = std::make_shared<T>();
|
|
||||||
}
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
bool ModelBase::fromHttpContent( std::shared_ptr<HttpContent> val, std::vector<T> & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if (val == nullptr) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, std::set<T>& outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if (val == nullptr) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
template <typename T>
|
|
||||||
bool ModelBase::fromHttpContent( std::shared_ptr<HttpContent> val, std::map<utility::string_t, T> & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if (val == nullptr) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_ModelBase_H_ */
|
|
||||||
|
|
@ -1,653 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/ModelBase.h"
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
ModelBase::ModelBase(): m_IsSet(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
ModelBase::~ModelBase()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
bool ModelBase::isSet() const
|
|
||||||
{
|
|
||||||
return m_IsSet;
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const bool val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
ss << val;
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const float val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
ss << val;
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const double val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
ss << val;
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const int32_t val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
ss << val;
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const int64_t val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
ss << val;
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString (const utility::string_t &val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
ss << val;
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const utility::datetime &val )
|
|
||||||
{
|
|
||||||
return val.to_string(utility::datetime::ISO_8601);
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const web::json::value &val )
|
|
||||||
{
|
|
||||||
return val.serialize();
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toString( const std::shared_ptr<HttpContent>& val )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss;
|
|
||||||
if( val != nullptr )
|
|
||||||
{
|
|
||||||
ss << val->getData();
|
|
||||||
}
|
|
||||||
return utility::string_t(ss.str());
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson(bool value)
|
|
||||||
{
|
|
||||||
return web::json::value::boolean(value);
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( float value )
|
|
||||||
{
|
|
||||||
return web::json::value::number(value);
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( double value )
|
|
||||||
{
|
|
||||||
return web::json::value::number(value);
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( int32_t value )
|
|
||||||
{
|
|
||||||
return web::json::value::number(value);
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( int64_t value )
|
|
||||||
{
|
|
||||||
return web::json::value::number(value);
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( const utility::string_t& value )
|
|
||||||
{
|
|
||||||
return web::json::value::string(value);
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( const utility::datetime& value )
|
|
||||||
{
|
|
||||||
return web::json::value::string(value.to_string(utility::datetime::ISO_8601));
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( const web::json::value& value )
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( const std::shared_ptr<HttpContent>& content )
|
|
||||||
{
|
|
||||||
web::json::value value;
|
|
||||||
if(content != nullptr)
|
|
||||||
{
|
|
||||||
value[utility::conversions::to_string_t("ContentDisposition")] = ModelBase::toJson(content->getContentDisposition());
|
|
||||||
value[utility::conversions::to_string_t("ContentType")] = ModelBase::toJson(content->getContentType());
|
|
||||||
value[utility::conversions::to_string_t("FileName")] = ModelBase::toJson(content->getFileName());
|
|
||||||
value[utility::conversions::to_string_t("InputStream")] = web::json::value::string( ModelBase::toBase64(content->getData()) );
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
web::json::value ModelBase::toJson( const std::shared_ptr<utility::datetime>& val )
|
|
||||||
{
|
|
||||||
web::json::value retVal;
|
|
||||||
if(val != nullptr)
|
|
||||||
{
|
|
||||||
retVal = toJson(*val);
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, bool &outVal )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss(val);
|
|
||||||
bool success = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ss >> outVal;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, float &outVal )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss(val);
|
|
||||||
bool success = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ss >> outVal;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
int64_t intVal = 0;
|
|
||||||
success = ModelBase::fromString(val, intVal);
|
|
||||||
if(success)
|
|
||||||
{
|
|
||||||
outVal = static_cast<float>(intVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, double &outVal )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss(val);
|
|
||||||
bool success = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ss >> outVal;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
int64_t intVal = 0;
|
|
||||||
success = ModelBase::fromString(val, intVal);
|
|
||||||
if(success)
|
|
||||||
{
|
|
||||||
outVal = static_cast<double>(intVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, int32_t &outVal )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss(val);
|
|
||||||
bool success = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ss >> outVal;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, int64_t &outVal )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss(val);
|
|
||||||
bool success = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ss >> outVal;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, utility::string_t &outVal )
|
|
||||||
{
|
|
||||||
utility::stringstream_t ss(val);
|
|
||||||
bool success = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ss >> outVal;
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, utility::datetime &outVal )
|
|
||||||
{
|
|
||||||
bool success = true;
|
|
||||||
auto dt = utility::datetime::from_string(val, utility::datetime::ISO_8601);
|
|
||||||
if( dt.is_initialized() )
|
|
||||||
{
|
|
||||||
outVal = dt;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, web::json::value &outVal )
|
|
||||||
{
|
|
||||||
outVal = web::json::value::parse(val);
|
|
||||||
return !outVal.is_null();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, std::shared_ptr<HttpContent>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = true;
|
|
||||||
if(outVal == nullptr)
|
|
||||||
{
|
|
||||||
outVal = std::shared_ptr<HttpContent>(new HttpContent());
|
|
||||||
}
|
|
||||||
if(outVal != nullptr)
|
|
||||||
{
|
|
||||||
outVal->setData(std::shared_ptr<std::istream>(new std::stringstream(utility::conversions::to_utf8string(val))));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromString( const utility::string_t& val, std::shared_ptr<utility::datetime>& outVal )
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
if(outVal == nullptr)
|
|
||||||
{
|
|
||||||
outVal = std::shared_ptr<utility::datetime>(new utility::datetime());
|
|
||||||
}
|
|
||||||
if( outVal != nullptr )
|
|
||||||
{
|
|
||||||
ok = fromJson(web::json::value::parse(val), *outVal);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, bool & outVal )
|
|
||||||
{
|
|
||||||
outVal = !val.is_boolean() ? false : val.as_bool();
|
|
||||||
return val.is_boolean();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, float & outVal )
|
|
||||||
{
|
|
||||||
outVal = (!val.is_double() && !val.is_integer()) ? std::numeric_limits<float>::quiet_NaN(): static_cast<float>(val.as_double());
|
|
||||||
return val.is_double() || val.is_integer();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, double & outVal )
|
|
||||||
{
|
|
||||||
outVal = (!val.is_double() && !val.is_integer()) ? std::numeric_limits<double>::quiet_NaN(): val.as_double();
|
|
||||||
return val.is_double() || val.is_integer();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, int32_t & outVal )
|
|
||||||
{
|
|
||||||
outVal = !val.is_integer() ? std::numeric_limits<int32_t>::quiet_NaN() : val.as_integer();
|
|
||||||
return val.is_integer();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, int64_t & outVal )
|
|
||||||
{
|
|
||||||
outVal = !val.is_number() ? std::numeric_limits<int64_t>::quiet_NaN() : val.as_number().to_int64();
|
|
||||||
return val.is_number();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, utility::string_t & outVal )
|
|
||||||
{
|
|
||||||
outVal = val.is_string() ? val.as_string() : utility::conversions::to_string_t("");
|
|
||||||
return val.is_string();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, utility::datetime & outVal )
|
|
||||||
{
|
|
||||||
outVal = val.is_null() ? utility::datetime::from_string(utility::conversions::to_string_t("NULL"), utility::datetime::ISO_8601) : utility::datetime::from_string(val.as_string(), utility::datetime::ISO_8601);
|
|
||||||
return outVal.is_initialized();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, web::json::value & outVal )
|
|
||||||
{
|
|
||||||
outVal = val;
|
|
||||||
return !val.is_null();
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, std::shared_ptr<HttpContent>& content )
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
if( content != nullptr)
|
|
||||||
{
|
|
||||||
result = true;
|
|
||||||
if(content == nullptr)
|
|
||||||
{
|
|
||||||
content = std::shared_ptr<HttpContent>(new HttpContent());
|
|
||||||
}
|
|
||||||
if(val.has_field(utility::conversions::to_string_t("ContentDisposition")))
|
|
||||||
{
|
|
||||||
utility::string_t value;
|
|
||||||
result = result && ModelBase::fromJson(val.at(utility::conversions::to_string_t("ContentDisposition")), value);
|
|
||||||
content->setContentDisposition( value );
|
|
||||||
}
|
|
||||||
if(val.has_field(utility::conversions::to_string_t("ContentType")))
|
|
||||||
{
|
|
||||||
utility::string_t value;
|
|
||||||
result = result && ModelBase::fromJson(val.at(utility::conversions::to_string_t("ContentType")), value);
|
|
||||||
content->setContentType( value );
|
|
||||||
}
|
|
||||||
if(val.has_field(utility::conversions::to_string_t("FileName")))
|
|
||||||
{
|
|
||||||
utility::string_t value;
|
|
||||||
result = result && ModelBase::fromJson(val.at(utility::conversions::to_string_t("FileName")), value);
|
|
||||||
content->setFileName( value );
|
|
||||||
}
|
|
||||||
if(val.has_field(utility::conversions::to_string_t("InputStream")))
|
|
||||||
{
|
|
||||||
utility::string_t value;
|
|
||||||
result = result && ModelBase::fromJson(val.at(utility::conversions::to_string_t("InputStream")), value);
|
|
||||||
content->setData( ModelBase::fromBase64( value ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromJson( const web::json::value& val, std::shared_ptr<utility::datetime> &outVal )
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
if(outVal == nullptr)
|
|
||||||
{
|
|
||||||
outVal = std::shared_ptr<utility::datetime>(new utility::datetime());
|
|
||||||
}
|
|
||||||
if( outVal != nullptr )
|
|
||||||
{
|
|
||||||
ok = fromJson(val, *outVal);
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, bool value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
std::stringstream* valueAsStringStream = new std::stringstream();
|
|
||||||
(*valueAsStringStream) << value;
|
|
||||||
content->setData( std::shared_ptr<std::istream>( valueAsStringStream ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, float value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
std::stringstream* valueAsStringStream = new std::stringstream();
|
|
||||||
(*valueAsStringStream) << value;
|
|
||||||
content->setData( std::shared_ptr<std::istream>( valueAsStringStream ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, double value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
std::stringstream* valueAsStringStream = new std::stringstream();
|
|
||||||
(*valueAsStringStream) << value;
|
|
||||||
content->setData( std::shared_ptr<std::istream>( valueAsStringStream ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, int32_t value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
std::stringstream* valueAsStringStream = new std::stringstream();
|
|
||||||
(*valueAsStringStream) << value;
|
|
||||||
content->setData( std::shared_ptr<std::istream>( valueAsStringStream ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, int64_t value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
std::stringstream* valueAsStringStream = new std::stringstream();
|
|
||||||
(*valueAsStringStream) << value;
|
|
||||||
content->setData( std::shared_ptr<std::istream>( valueAsStringStream) ) ;
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const utility::string_t& value, const utility::string_t& contentType)
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content(new HttpContent);
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::shared_ptr<std::istream>( new std::stringstream( utility::conversions::to_utf8string(value) ) ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const utility::datetime& value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::shared_ptr<std::istream>( new std::stringstream( utility::conversions::to_utf8string(value.to_string(utility::datetime::ISO_8601) ) ) ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const web::json::value& value, const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::shared_ptr<std::istream>( new std::stringstream( utility::conversions::to_utf8string(value.serialize()) ) ) );
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent( const utility::string_t& name, const std::shared_ptr<HttpContent>& value )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
if( value != nullptr )
|
|
||||||
{
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( value->getContentDisposition() );
|
|
||||||
content->setContentType( value->getContentType() );
|
|
||||||
content->setData( value->getData() );
|
|
||||||
content->setFileName( value->getFileName() );
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
std::shared_ptr<HttpContent> ModelBase::toHttpContent(const utility::string_t& name, const std::shared_ptr<utility::datetime>& value , const utility::string_t& contentType )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content( new HttpContent );
|
|
||||||
if (value != nullptr )
|
|
||||||
{
|
|
||||||
content->setName( name );
|
|
||||||
content->setContentDisposition( utility::conversions::to_string_t("form-data") );
|
|
||||||
content->setContentType( contentType );
|
|
||||||
content->setData( std::shared_ptr<std::istream>( new std::stringstream( utility::conversions::to_utf8string( toJson(*value).serialize() ) ) ) );
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, bool & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, float & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, double & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, int32_t & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, int64_t & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, utility::string_t & outVal )
|
|
||||||
{
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
std::shared_ptr<std::istream> data = val->getData();
|
|
||||||
data->seekg( 0, data->beg );
|
|
||||||
|
|
||||||
std::string str((std::istreambuf_iterator<char>(*data.get())),
|
|
||||||
std::istreambuf_iterator<char>());
|
|
||||||
outVal = utility::conversions::to_string_t(str);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, utility::datetime & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
outVal = utility::datetime::from_string(str, utility::datetime::ISO_8601);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, web::json::value & outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
bool ModelBase::fromHttpContent(std::shared_ptr<HttpContent> val, std::shared_ptr<HttpContent>& outVal )
|
|
||||||
{
|
|
||||||
utility::string_t str;
|
|
||||||
if( val == nullptr ) return false;
|
|
||||||
if( outVal == nullptr )
|
|
||||||
{
|
|
||||||
outVal = std::shared_ptr<HttpContent>(new HttpContent());
|
|
||||||
}
|
|
||||||
ModelBase::fromHttpContent(val, str);
|
|
||||||
return fromString(str, outVal);
|
|
||||||
}
|
|
||||||
// base64 encoding/decoding based on : https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C.2B.2B
|
|
||||||
const static char Base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
const static char Base64PadChar = '=';
|
|
||||||
utility::string_t ModelBase::toBase64( utility::string_t value )
|
|
||||||
{
|
|
||||||
std::shared_ptr<std::istream> source( new std::stringstream( utility::conversions::to_utf8string(value) ) );
|
|
||||||
return ModelBase::toBase64(source);
|
|
||||||
}
|
|
||||||
utility::string_t ModelBase::toBase64( std::shared_ptr<std::istream> value )
|
|
||||||
{
|
|
||||||
value->seekg( 0, value->end );
|
|
||||||
size_t length = value->tellg();
|
|
||||||
value->seekg( 0, value->beg );
|
|
||||||
utility::string_t base64;
|
|
||||||
base64.reserve( ((length / 3) + (length % 3 > 0)) * 4 );
|
|
||||||
char read[3] = { 0 };
|
|
||||||
uint32_t temp;
|
|
||||||
for ( size_t idx = 0; idx < length / 3; idx++ )
|
|
||||||
{
|
|
||||||
value->read( read, 3 );
|
|
||||||
temp = (read[0]) << 16;
|
|
||||||
temp += (read[1]) << 8;
|
|
||||||
temp += (read[2]);
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x00FC0000) >> 18] );
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x0003F000) >> 12] );
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x00000FC0) >> 6] );
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x0000003F)] );
|
|
||||||
}
|
|
||||||
switch ( length % 3 )
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
value->read( read, 1 );
|
|
||||||
temp = read[0] << 16;
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x00FC0000) >> 18] );
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x0003F000) >> 12] );
|
|
||||||
base64.append( 2, Base64PadChar );
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
value->read( read, 2 );
|
|
||||||
temp = read[0] << 16;
|
|
||||||
temp += read[1] << 8;
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x00FC0000) >> 18] );
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x0003F000) >> 12] );
|
|
||||||
base64.append( 1, Base64Chars[(temp & 0x00000FC0) >> 6] );
|
|
||||||
base64.append( 1, Base64PadChar );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return base64;
|
|
||||||
}
|
|
||||||
std::shared_ptr<std::istream> ModelBase::fromBase64( const utility::string_t& encoded )
|
|
||||||
{
|
|
||||||
std::shared_ptr<std::stringstream> result(new std::stringstream);
|
|
||||||
|
|
||||||
char outBuf[3] = { 0 };
|
|
||||||
uint32_t temp = 0;
|
|
||||||
|
|
||||||
utility::string_t::const_iterator cursor = encoded.begin();
|
|
||||||
while ( cursor < encoded.end() )
|
|
||||||
{
|
|
||||||
for ( size_t quantumPosition = 0; quantumPosition < 4; quantumPosition++ )
|
|
||||||
{
|
|
||||||
temp <<= 6;
|
|
||||||
if ( *cursor >= 0x41 && *cursor <= 0x5A )
|
|
||||||
{
|
|
||||||
temp |= *cursor - 0x41;
|
|
||||||
}
|
|
||||||
else if ( *cursor >= 0x61 && *cursor <= 0x7A )
|
|
||||||
{
|
|
||||||
temp |= *cursor - 0x47;
|
|
||||||
}
|
|
||||||
else if ( *cursor >= 0x30 && *cursor <= 0x39 )
|
|
||||||
{
|
|
||||||
temp |= *cursor + 0x04;
|
|
||||||
}
|
|
||||||
else if ( *cursor == 0x2B )
|
|
||||||
{
|
|
||||||
temp |= 0x3E; //change to 0x2D for URL alphabet
|
|
||||||
}
|
|
||||||
else if ( *cursor == 0x2F )
|
|
||||||
{
|
|
||||||
temp |= 0x3F; //change to 0x5F for URL alphabet
|
|
||||||
}
|
|
||||||
else if ( *cursor == Base64PadChar ) //pad
|
|
||||||
{
|
|
||||||
switch ( encoded.end() - cursor )
|
|
||||||
{
|
|
||||||
case 1: //One pad character
|
|
||||||
outBuf[0] = (temp >> 16) & 0x000000FF;
|
|
||||||
outBuf[1] = (temp >> 8) & 0x000000FF;
|
|
||||||
result->write( outBuf, 2 );
|
|
||||||
return result;
|
|
||||||
case 2: //Two pad characters
|
|
||||||
outBuf[0] = (temp >> 10) & 0x000000FF;
|
|
||||||
result->write( outBuf, 1 );
|
|
||||||
return result;
|
|
||||||
default:
|
|
||||||
throw web::json::json_exception( utility::conversions::to_string_t( "Invalid Padding in Base 64!" ).c_str() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw web::json::json_exception( utility::conversions::to_string_t( "Non-Valid Character in Base 64!" ).c_str() );
|
|
||||||
}
|
|
||||||
++cursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
outBuf[0] = (temp >> 16) & 0x000000FF;
|
|
||||||
outBuf[1] = (temp >> 8) & 0x000000FF;
|
|
||||||
outBuf[2] = (temp) & 0x000000FF;
|
|
||||||
result->write( outBuf, 3 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* MultipartFormData.h
|
|
||||||
*
|
|
||||||
* This class represents a container for building application/x-multipart-formdata requests.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_MultipartFormData_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_MultipartFormData_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include "{{packageName}}/IHttpBody.h"
|
|
||||||
#include "{{packageName}}/HttpContent.h"
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} MultipartFormData
|
|
||||||
: public IHttpBody
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MultipartFormData();
|
|
||||||
MultipartFormData(const utility::string_t& boundary);
|
|
||||||
virtual ~MultipartFormData();
|
|
||||||
|
|
||||||
virtual void add( std::shared_ptr<HttpContent> content );
|
|
||||||
virtual utility::string_t getBoundary();
|
|
||||||
virtual std::shared_ptr<HttpContent> getContent(const utility::string_t& name) const;
|
|
||||||
virtual bool hasContent(const utility::string_t& name) const;
|
|
||||||
virtual void writeTo( std::ostream& target );
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<std::shared_ptr<HttpContent>> m_Contents;
|
|
||||||
utility::string_t m_Boundary;
|
|
||||||
std::map<utility::string_t, std::shared_ptr<HttpContent>> m_ContentLookup;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_MultipartFormData_H_ */
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/MultipartFormData.h"
|
|
||||||
#include "{{packageName}}/ModelBase.h"
|
|
||||||
|
|
||||||
#include <boost/uuid/random_generator.hpp>
|
|
||||||
#include <boost/uuid/uuid_io.hpp>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
MultipartFormData::MultipartFormData()
|
|
||||||
{
|
|
||||||
utility::stringstream_t uuidString;
|
|
||||||
uuidString << boost::uuids::random_generator()();
|
|
||||||
m_Boundary = uuidString.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
MultipartFormData::MultipartFormData(const utility::string_t& boundary)
|
|
||||||
: m_Boundary(boundary)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
MultipartFormData::~MultipartFormData()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t MultipartFormData::getBoundary()
|
|
||||||
{
|
|
||||||
return m_Boundary;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultipartFormData::add( std::shared_ptr<HttpContent> content )
|
|
||||||
{
|
|
||||||
m_Contents.push_back( content );
|
|
||||||
m_ContentLookup[content->getName()] = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MultipartFormData::hasContent(const utility::string_t& name) const
|
|
||||||
{
|
|
||||||
return m_ContentLookup.find(name) != m_ContentLookup.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<HttpContent> MultipartFormData::getContent(const utility::string_t& name) const
|
|
||||||
{
|
|
||||||
auto result = m_ContentLookup.find(name);
|
|
||||||
if(result == m_ContentLookup.end())
|
|
||||||
{
|
|
||||||
return std::shared_ptr<HttpContent>(nullptr);
|
|
||||||
}
|
|
||||||
return result->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultipartFormData::writeTo( std::ostream& target )
|
|
||||||
{
|
|
||||||
for ( size_t i = 0; i < m_Contents.size(); i++ )
|
|
||||||
{
|
|
||||||
std::shared_ptr<HttpContent> content = m_Contents[i];
|
|
||||||
|
|
||||||
// boundary
|
|
||||||
target << "\r\n" << "--" << utility::conversions::to_utf8string( m_Boundary ) << "\r\n";
|
|
||||||
|
|
||||||
// headers
|
|
||||||
target << "Content-Disposition: " << utility::conversions::to_utf8string( content->getContentDisposition() );
|
|
||||||
if ( content->getName().size() > 0 )
|
|
||||||
{
|
|
||||||
target << "; name=\"" << utility::conversions::to_utf8string( content->getName() ) << "\"";
|
|
||||||
}
|
|
||||||
if ( content->getFileName().size() > 0 )
|
|
||||||
{
|
|
||||||
target << "; filename=\"" << utility::conversions::to_utf8string( content->getFileName() ) << "\"";
|
|
||||||
}
|
|
||||||
target << "\r\n";
|
|
||||||
|
|
||||||
if ( content->getContentType().size() > 0 )
|
|
||||||
{
|
|
||||||
target << "Content-Type: " << utility::conversions::to_utf8string( content->getContentType() ) << "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
target << "\r\n";
|
|
||||||
|
|
||||||
// body
|
|
||||||
std::shared_ptr<std::istream> data = content->getData();
|
|
||||||
|
|
||||||
data->seekg( 0, data->end );
|
|
||||||
std::vector<char> dataBytes( data->tellg() );
|
|
||||||
|
|
||||||
data->seekg( 0, data->beg );
|
|
||||||
data->read( &dataBytes[0], dataBytes.size() );
|
|
||||||
|
|
||||||
std::copy( dataBytes.begin(), dataBytes.end(), std::ostreambuf_iterator<char>( target ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
target << "\r\n--" << utility::conversions::to_utf8string( m_Boundary ) << "--\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
/*
|
|
||||||
* Object.h
|
|
||||||
*
|
|
||||||
* This is the implementation of a JSON object.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef {{modelHeaderGuardPrefix}}_Object_H_
|
|
||||||
#define {{modelHeaderGuardPrefix}}_Object_H_
|
|
||||||
|
|
||||||
{{{defaultInclude}}}
|
|
||||||
#include "{{packageName}}/ModelBase.h"
|
|
||||||
|
|
||||||
#include <cpprest/details/basic_types.h>
|
|
||||||
#include <cpprest/json.h>
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
class {{declspec}} Object : public ModelBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Object();
|
|
||||||
virtual ~Object();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// ModelBase overrides
|
|
||||||
void validate() override;
|
|
||||||
|
|
||||||
web::json::value toJson() const override;
|
|
||||||
bool fromJson(const web::json::value& json) override;
|
|
||||||
|
|
||||||
void toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) const override;
|
|
||||||
bool fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& namePrefix) override;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
/// Object manipulation
|
|
||||||
web::json::value getValue(const utility::string_t& key) const;
|
|
||||||
void setValue(const utility::string_t& key, const web::json::value& value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
web::json::value m_object;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
#endif /* {{modelHeaderGuardPrefix}}_Object_H_ */
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
{{>licenseInfo}}
|
|
||||||
#include "{{packageName}}/Object.h"
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
namespace {{this}} {
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
||||||
Object::Object()
|
|
||||||
{
|
|
||||||
m_object = web::json::value::object();
|
|
||||||
}
|
|
||||||
|
|
||||||
Object::~Object()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Object::validate()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
web::json::value Object::toJson() const
|
|
||||||
{
|
|
||||||
return m_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Object::fromJson(const web::json::value& val)
|
|
||||||
{
|
|
||||||
if (val.is_object())
|
|
||||||
{
|
|
||||||
m_object = val;
|
|
||||||
m_IsSet = true;
|
|
||||||
}
|
|
||||||
return isSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Object::toMultipart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix) const
|
|
||||||
{
|
|
||||||
utility::string_t namePrefix = prefix;
|
|
||||||
if(namePrefix.size() > 0 && namePrefix.substr(namePrefix.size() - 1) != utility::conversions::to_string_t("."))
|
|
||||||
{
|
|
||||||
namePrefix += utility::conversions::to_string_t(".");
|
|
||||||
}
|
|
||||||
multipart->add(ModelBase::toHttpContent(namePrefix + utility::conversions::to_string_t("object"), m_object));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Object::fromMultiPart(std::shared_ptr<MultipartFormData> multipart, const utility::string_t& prefix)
|
|
||||||
{
|
|
||||||
utility::string_t namePrefix = prefix;
|
|
||||||
if(namePrefix.size() > 0 && namePrefix.substr(namePrefix.size() - 1) != utility::conversions::to_string_t("."))
|
|
||||||
{
|
|
||||||
namePrefix += utility::conversions::to_string_t(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ModelBase::fromHttpContent(multipart->getContent(namePrefix + utility::conversions::to_string_t("object")), m_object ) )
|
|
||||||
{
|
|
||||||
m_IsSet = true;
|
|
||||||
}
|
|
||||||
return isSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
web::json::value Object::getValue(const utility::string_t& key) const
|
|
||||||
{
|
|
||||||
return m_object.at(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Object::setValue(const utility::string_t& key, const web::json::value& value)
|
|
||||||
{
|
|
||||||
if( !value.is_null() )
|
|
||||||
{
|
|
||||||
m_object[key] = value;
|
|
||||||
m_IsSet = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{#modelNamespaceDeclarations}}
|
|
||||||
}
|
|
||||||
{{/modelNamespaceDeclarations}}
|
|
||||||
|
|
@ -639,13 +639,20 @@ components:
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
|
Tag:
|
||||||
|
description: 'A localized tag: keys are language codes (ISO 639-1), values are tag names'
|
||||||
|
type: object
|
||||||
|
example:
|
||||||
|
en: Shojo
|
||||||
|
ru: Сёдзё
|
||||||
|
ja: 少女
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
Tags:
|
Tags:
|
||||||
description: Array of localized tags
|
description: Array of localized tags
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
$ref: '#/components/schemas/Tag'
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
example:
|
example:
|
||||||
- en: Shojo
|
- en: Shojo
|
||||||
ru: Сёдзё
|
ru: Сёдзё
|
||||||
|
|
@ -793,7 +800,7 @@ components:
|
||||||
review_id:
|
review_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
ctime:
|
ftime:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
required:
|
required:
|
||||||
|
|
@ -817,7 +824,7 @@ components:
|
||||||
review_id:
|
review_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
ctime:
|
ftime:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
required:
|
required:
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ type User struct {
|
||||||
|
|
||||||
// UserTitle defines model for UserTitle.
|
// UserTitle defines model for UserTitle.
|
||||||
type UserTitle struct {
|
type UserTitle struct {
|
||||||
Ctime *time.Time `json:"ctime,omitempty"`
|
Ftime *time.Time `json:"ftime,omitempty"`
|
||||||
Rate *int32 `json:"rate,omitempty"`
|
Rate *int32 `json:"rate,omitempty"`
|
||||||
ReviewId *int64 `json:"review_id,omitempty"`
|
ReviewId *int64 `json:"review_id,omitempty"`
|
||||||
|
|
||||||
|
|
@ -168,7 +168,7 @@ type UserTitle struct {
|
||||||
|
|
||||||
// UserTitleMini defines model for UserTitleMini.
|
// UserTitleMini defines model for UserTitleMini.
|
||||||
type UserTitleMini struct {
|
type UserTitleMini struct {
|
||||||
Ctime *time.Time `json:"ctime,omitempty"`
|
Ftime *time.Time `json:"ftime,omitempty"`
|
||||||
Rate *int32 `json:"rate,omitempty"`
|
Rate *int32 `json:"rate,omitempty"`
|
||||||
ReviewId *int64 `json:"review_id,omitempty"`
|
ReviewId *int64 `json:"review_id,omitempty"`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@ properties:
|
||||||
review_id:
|
review_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
ctime:
|
ftime:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,6 @@ properties:
|
||||||
review_id:
|
review_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
ctime:
|
ftime:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
|
|
||||||
154
auth/auth.gen.go
154
auth/auth.gen.go
|
|
@ -56,6 +56,12 @@ type ServerInterface interface {
|
||||||
// Get service impersontaion token
|
// Get service impersontaion token
|
||||||
// (POST /get-impersonation-token)
|
// (POST /get-impersonation-token)
|
||||||
GetImpersonationToken(c *gin.Context)
|
GetImpersonationToken(c *gin.Context)
|
||||||
|
// Logs out the user
|
||||||
|
// (POST /logout)
|
||||||
|
Logout(c *gin.Context)
|
||||||
|
// Refreshes access_token and refresh_token
|
||||||
|
// (GET /refresh-tokens)
|
||||||
|
RefreshTokens(c *gin.Context)
|
||||||
// Sign in a user and return JWT
|
// Sign in a user and return JWT
|
||||||
// (POST /sign-in)
|
// (POST /sign-in)
|
||||||
PostSignIn(c *gin.Context)
|
PostSignIn(c *gin.Context)
|
||||||
|
|
@ -88,6 +94,32 @@ func (siw *ServerInterfaceWrapper) GetImpersonationToken(c *gin.Context) {
|
||||||
siw.Handler.GetImpersonationToken(c)
|
siw.Handler.GetImpersonationToken(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) Logout(c *gin.Context) {
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.Logout(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshTokens operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) RefreshTokens(c *gin.Context) {
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.RefreshTokens(c)
|
||||||
|
}
|
||||||
|
|
||||||
// PostSignIn operation middleware
|
// PostSignIn operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) PostSignIn(c *gin.Context) {
|
func (siw *ServerInterfaceWrapper) PostSignIn(c *gin.Context) {
|
||||||
|
|
||||||
|
|
@ -142,10 +174,18 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options
|
||||||
}
|
}
|
||||||
|
|
||||||
router.POST(options.BaseURL+"/get-impersonation-token", wrapper.GetImpersonationToken)
|
router.POST(options.BaseURL+"/get-impersonation-token", wrapper.GetImpersonationToken)
|
||||||
|
router.POST(options.BaseURL+"/logout", wrapper.Logout)
|
||||||
|
router.GET(options.BaseURL+"/refresh-tokens", wrapper.RefreshTokens)
|
||||||
router.POST(options.BaseURL+"/sign-in", wrapper.PostSignIn)
|
router.POST(options.BaseURL+"/sign-in", wrapper.PostSignIn)
|
||||||
router.POST(options.BaseURL+"/sign-up", wrapper.PostSignUp)
|
router.POST(options.BaseURL+"/sign-up", wrapper.PostSignUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientErrorResponse struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerErrorResponse struct {
|
||||||
|
}
|
||||||
|
|
||||||
type UnauthorizedErrorResponse struct {
|
type UnauthorizedErrorResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,6 +216,64 @@ func (response GetImpersonationToken401Response) VisitGetImpersonationTokenRespo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogoutRequestObject struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogoutResponseObject interface {
|
||||||
|
VisitLogoutResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logout200Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response Logout200Response) VisitLogoutResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logout500Response = ServerErrorResponse
|
||||||
|
|
||||||
|
func (response Logout500Response) VisitLogoutResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokensRequestObject struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokensResponseObject interface {
|
||||||
|
VisitRefreshTokensResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokens200Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response RefreshTokens200Response) VisitRefreshTokensResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokens400Response = ClientErrorResponse
|
||||||
|
|
||||||
|
func (response RefreshTokens400Response) VisitRefreshTokensResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokens401Response = UnauthorizedErrorResponse
|
||||||
|
|
||||||
|
func (response RefreshTokens401Response) VisitRefreshTokensResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokens500Response = ServerErrorResponse
|
||||||
|
|
||||||
|
func (response RefreshTokens500Response) VisitRefreshTokensResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type PostSignInRequestObject struct {
|
type PostSignInRequestObject struct {
|
||||||
Body *PostSignInJSONRequestBody
|
Body *PostSignInJSONRequestBody
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +325,12 @@ type StrictServerInterface interface {
|
||||||
// Get service impersontaion token
|
// Get service impersontaion token
|
||||||
// (POST /get-impersonation-token)
|
// (POST /get-impersonation-token)
|
||||||
GetImpersonationToken(ctx context.Context, request GetImpersonationTokenRequestObject) (GetImpersonationTokenResponseObject, error)
|
GetImpersonationToken(ctx context.Context, request GetImpersonationTokenRequestObject) (GetImpersonationTokenResponseObject, error)
|
||||||
|
// Logs out the user
|
||||||
|
// (POST /logout)
|
||||||
|
Logout(ctx context.Context, request LogoutRequestObject) (LogoutResponseObject, error)
|
||||||
|
// Refreshes access_token and refresh_token
|
||||||
|
// (GET /refresh-tokens)
|
||||||
|
RefreshTokens(ctx context.Context, request RefreshTokensRequestObject) (RefreshTokensResponseObject, error)
|
||||||
// Sign in a user and return JWT
|
// Sign in a user and return JWT
|
||||||
// (POST /sign-in)
|
// (POST /sign-in)
|
||||||
PostSignIn(ctx context.Context, request PostSignInRequestObject) (PostSignInResponseObject, error)
|
PostSignIn(ctx context.Context, request PostSignInRequestObject) (PostSignInResponseObject, error)
|
||||||
|
|
@ -280,6 +384,56 @@ func (sh *strictHandler) GetImpersonationToken(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout operation middleware
|
||||||
|
func (sh *strictHandler) Logout(ctx *gin.Context) {
|
||||||
|
var request LogoutRequestObject
|
||||||
|
|
||||||
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.Logout(ctx, request.(LogoutRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "Logout")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(LogoutResponseObject); ok {
|
||||||
|
if err := validResponse.VisitLogoutResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshTokens operation middleware
|
||||||
|
func (sh *strictHandler) RefreshTokens(ctx *gin.Context) {
|
||||||
|
var request RefreshTokensRequestObject
|
||||||
|
|
||||||
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.RefreshTokens(ctx, request.(RefreshTokensRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "RefreshTokens")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(RefreshTokensResponseObject); ok {
|
||||||
|
if err := validResponse.VisitRefreshTokensResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PostSignIn operation middleware
|
// PostSignIn operation middleware
|
||||||
func (sh *strictHandler) PostSignIn(ctx *gin.Context) {
|
func (sh *strictHandler) PostSignIn(ctx *gin.Context) {
|
||||||
var request PostSignInRequestObject
|
var request PostSignInRequestObject
|
||||||
|
|
|
||||||
9
auth/claims.go
Normal file
9
auth/claims.go
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import "github.com/golang-jwt/jwt/v5"
|
||||||
|
|
||||||
|
type TokenClaims struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ImpID *string `json:"imp_id,omitempty"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
@ -116,6 +116,33 @@ paths:
|
||||||
"401":
|
"401":
|
||||||
$ref: '#/components/responses/UnauthorizedError'
|
$ref: '#/components/responses/UnauthorizedError'
|
||||||
|
|
||||||
|
/refresh-tokens:
|
||||||
|
get:
|
||||||
|
summary: Refreshes access_token and refresh_token
|
||||||
|
operationId: refreshTokens
|
||||||
|
tags: [Auth]
|
||||||
|
responses:
|
||||||
|
# This one sets two cookies: access_token and refresh_token
|
||||||
|
"200":
|
||||||
|
description: Refresh success
|
||||||
|
"400":
|
||||||
|
$ref: '#/components/responses/ClientError'
|
||||||
|
"401":
|
||||||
|
$ref: '#/components/responses/UnauthorizedError'
|
||||||
|
"500":
|
||||||
|
$ref: '#/components/responses/ServerError'
|
||||||
|
|
||||||
|
/logout:
|
||||||
|
post:
|
||||||
|
summary: Logs out the user
|
||||||
|
operationId: logout
|
||||||
|
tags: [Auth]
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Logout success
|
||||||
|
"500":
|
||||||
|
$ref: '#/components/responses/ServerError'
|
||||||
|
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
|
|
@ -123,4 +150,8 @@ components:
|
||||||
scheme: bearer
|
scheme: bearer
|
||||||
responses:
|
responses:
|
||||||
UnauthorizedError:
|
UnauthorizedError:
|
||||||
description: Access token is missing or invalid
|
description: Access token is missing or invalid
|
||||||
|
ServerError:
|
||||||
|
description: ServerError
|
||||||
|
ClientError:
|
||||||
|
description: ClientError
|
||||||
|
|
@ -67,6 +67,9 @@ services:
|
||||||
RABBITMQ_URL: ${RABBITMQ_URL}
|
RABBITMQ_URL: ${RABBITMQ_URL}
|
||||||
JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
|
JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
|
||||||
AUTH_ENABLED: ${AUTH_ENABLED}
|
AUTH_ENABLED: ${AUTH_ENABLED}
|
||||||
|
IMAGES_BASE_URL: http://nyanimedb-images:8000
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
# ports:
|
# ports:
|
||||||
# - "8080:8080"
|
# - "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
@ -102,9 +105,43 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- nyanimedb-network
|
- nyanimedb-network
|
||||||
|
|
||||||
|
nyanimedb-etl:
|
||||||
|
image: meowgit.nekoea.red/nihonium/nyanimedb-etl:latest
|
||||||
|
container_name: nyanimedb-etl
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: ${DATABASE_URL}
|
||||||
|
RABBITMQ_URL: ${RABBITMQ_URL}
|
||||||
|
NYANIMEDB_IMPORT_RPC_QUEUE: anime_import_rpc
|
||||||
|
NYANIMEDB_IMAGE_SERVICE_URL: http://nyanimedb-images:8000
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- rabbitmq
|
||||||
|
- nyanimedb-images
|
||||||
|
networks:
|
||||||
|
- nyanimedb-network
|
||||||
|
|
||||||
|
nyanimedb-images:
|
||||||
|
image: meowgit.nekoea.red/nihonium/nyanimedb-image-storage:latest
|
||||||
|
container_name: nyanimedb-images
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
NYANIMEDB_MEDIA_ROOT: /media
|
||||||
|
volumes:
|
||||||
|
- media_data:/media
|
||||||
|
networks:
|
||||||
|
- nyanimedb-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8000/docs || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
rabbitmq_data:
|
rabbitmq_data:
|
||||||
|
media_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|
|
||||||
2
modules/anime_etl/.gitignore
vendored
Normal file
2
modules/anime_etl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
1
modules/anime_etl/.python-version
Normal file
1
modules/anime_etl/.python-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.12
|
||||||
0
modules/anime_etl/README.md
Normal file
0
modules/anime_etl/README.md
Normal file
0
modules/anime_etl/__init__.py
Normal file
0
modules/anime_etl/__init__.py
Normal file
26
modules/anime_etl/canonicalizer.py
Normal file
26
modules/anime_etl/canonicalizer.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# anime_etl/canonicalizer.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from models import SourceTitle, CanonicalTitle
|
||||||
|
|
||||||
|
|
||||||
|
def source_title_to_canonical(src: SourceTitle) -> CanonicalTitle:
|
||||||
|
return CanonicalTitle(
|
||||||
|
id=None,
|
||||||
|
title_names=src.title_names,
|
||||||
|
studio=src.studio,
|
||||||
|
tags=list(src.tags),
|
||||||
|
poster=src.poster,
|
||||||
|
|
||||||
|
title_status=src.title_status,
|
||||||
|
rating=src.rating,
|
||||||
|
rating_count=src.rating_count,
|
||||||
|
|
||||||
|
release_year=src.release_year,
|
||||||
|
release_season=src.release_season,
|
||||||
|
|
||||||
|
season=src.season,
|
||||||
|
episodes_aired=src.episodes_aired,
|
||||||
|
episodes_all=src.episodes_all,
|
||||||
|
episodes_len=src.episodes_len,
|
||||||
|
)
|
||||||
254
modules/anime_etl/db/repository.py
Normal file
254
modules/anime_etl/db/repository.py
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
# anime_etl/db/repository.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Optional, Dict, List
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
from psycopg.rows import dict_row
|
||||||
|
|
||||||
|
from models import CanonicalTitle, Studio, Image
|
||||||
|
from images.downloader import ensure_image_downloaded
|
||||||
|
|
||||||
|
|
||||||
|
Conn = psycopg.AsyncConnection
|
||||||
|
|
||||||
|
|
||||||
|
def _choose_primary_name(
|
||||||
|
title_names: Dict[str, List[str]],
|
||||||
|
) -> Optional[tuple[str, str]]:
|
||||||
|
# (lang, name)
|
||||||
|
for lang in ("en", "romaji", "ja"):
|
||||||
|
variants = title_names.get(lang) or []
|
||||||
|
if variants:
|
||||||
|
return lang, variants[0]
|
||||||
|
|
||||||
|
for lang, variants in title_names.items():
|
||||||
|
if variants:
|
||||||
|
return lang, variants[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
_SHA1_PATH_RE = re.compile(
|
||||||
|
r"^[a-zA-Z0-9_-]+/[0-9a-f]{2}/[0-9a-f]{2}/[0-9a-f]{40}\.[a-zA-Z0-9]{1,5}$"
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_normalized_image_path(path: str) -> bool:
|
||||||
|
return bool(_SHA1_PATH_RE.match(path))
|
||||||
|
|
||||||
|
|
||||||
|
async def get_or_create_image(
|
||||||
|
conn: Conn,
|
||||||
|
img: Optional[Image],
|
||||||
|
*,
|
||||||
|
subdir: str = "posters",
|
||||||
|
) -> Optional[int]:
|
||||||
|
if img is None or not img.image_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
url = img.image_path
|
||||||
|
|
||||||
|
# 1) Если это URL → скачиваем через image-service
|
||||||
|
if url.startswith("http://") or url.startswith("https://"):
|
||||||
|
try:
|
||||||
|
rel_path = await ensure_image_downloaded(url, subdir=subdir)
|
||||||
|
except Exception as e:
|
||||||
|
# не удалось скачать картинку — просто пропускаем
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
# неправильный формат — просто пропуск
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 3) Проверка в базе
|
||||||
|
async with conn.cursor(row_factory=dict_row) as cur:
|
||||||
|
await cur.execute(
|
||||||
|
"SELECT id FROM images WHERE image_path = %s",
|
||||||
|
(rel_path,),
|
||||||
|
)
|
||||||
|
row = await cur.fetchone()
|
||||||
|
if row:
|
||||||
|
return row["id"]
|
||||||
|
|
||||||
|
# 4) Вставляем запись
|
||||||
|
await cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO images (storage_type, image_path)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
RETURNING id
|
||||||
|
""",
|
||||||
|
("local", rel_path),
|
||||||
|
)
|
||||||
|
row = await cur.fetchone()
|
||||||
|
return row["id"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def get_or_create_studio(
|
||||||
|
conn: Conn,
|
||||||
|
studio: Optional[Studio],
|
||||||
|
) -> Optional[int]:
|
||||||
|
if studio is None or not studio.name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async with conn.cursor(row_factory=dict_row) as cur:
|
||||||
|
# 1. Сначала ищем студию
|
||||||
|
await cur.execute(
|
||||||
|
"SELECT id, illust_id, studio_desc FROM studios WHERE studio_name = %s",
|
||||||
|
(studio.name,),
|
||||||
|
)
|
||||||
|
row = await cur.fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
studio_id = row["id"]
|
||||||
|
illust_id = row["illust_id"]
|
||||||
|
studio_desc = row["studio_desc"]
|
||||||
|
|
||||||
|
# 1a. Если нет illust_id, а нам пришёл постер — докачаем и обновим
|
||||||
|
if illust_id is None and studio.poster is not None:
|
||||||
|
illust_id = await get_or_create_image(conn, studio.poster, subdir="studios")
|
||||||
|
await cur.execute(
|
||||||
|
"UPDATE studios SET illust_id = %s WHERE id = %s",
|
||||||
|
(illust_id, studio_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1b. Если нет описания, а enrich уже поднял description — обновим описание
|
||||||
|
if studio_desc is None and studio.description:
|
||||||
|
await cur.execute(
|
||||||
|
"UPDATE studios SET studio_desc = %s WHERE id = %s",
|
||||||
|
(studio.description, studio_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
return studio_id
|
||||||
|
|
||||||
|
# 2. Студии нет — создаём
|
||||||
|
illust_id: Optional[int] = None
|
||||||
|
if studio.poster is not None:
|
||||||
|
illust_id = await get_or_create_image(conn, studio.poster, subdir="studios")
|
||||||
|
|
||||||
|
await cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO studios (studio_name, illust_id, studio_desc)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
RETURNING id
|
||||||
|
""",
|
||||||
|
(studio.name, illust_id, studio.description),
|
||||||
|
)
|
||||||
|
row = await cur.fetchone()
|
||||||
|
return row["id"]
|
||||||
|
|
||||||
|
async def find_title_id_by_name_and_year(
|
||||||
|
conn: Conn,
|
||||||
|
title_names: Dict[str, List[str]],
|
||||||
|
release_year: Optional[int],
|
||||||
|
) -> Optional[int]:
|
||||||
|
if release_year is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
pair = _choose_primary_name(title_names)
|
||||||
|
if not pair:
|
||||||
|
return None
|
||||||
|
|
||||||
|
lang, primary_name = pair
|
||||||
|
probe = json.dumps({lang: [primary_name]})
|
||||||
|
|
||||||
|
async with conn.cursor(row_factory=dict_row) as cur:
|
||||||
|
await cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT id
|
||||||
|
FROM titles
|
||||||
|
WHERE release_year = %s
|
||||||
|
AND title_names @> %s::jsonb
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(release_year, probe),
|
||||||
|
)
|
||||||
|
row = await cur.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return row["id"]
|
||||||
|
|
||||||
|
|
||||||
|
async def insert_title(
|
||||||
|
conn: Conn,
|
||||||
|
title: CanonicalTitle,
|
||||||
|
studio_id: Optional[int],
|
||||||
|
poster_id: Optional[int],
|
||||||
|
) -> int:
|
||||||
|
episodes_len_json = (
|
||||||
|
json.dumps(title.episodes_len) if title.episodes_len is not None else None
|
||||||
|
)
|
||||||
|
|
||||||
|
async with conn.cursor(row_factory=dict_row) as cur:
|
||||||
|
await cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO titles (
|
||||||
|
title_names,
|
||||||
|
studio_id,
|
||||||
|
poster_id,
|
||||||
|
title_status,
|
||||||
|
rating,
|
||||||
|
rating_count,
|
||||||
|
release_year,
|
||||||
|
release_season,
|
||||||
|
season,
|
||||||
|
episodes_aired,
|
||||||
|
episodes_all,
|
||||||
|
episodes_len
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
%s::jsonb,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s::jsonb
|
||||||
|
)
|
||||||
|
RETURNING id
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
json.dumps(title.title_names),
|
||||||
|
studio_id,
|
||||||
|
poster_id,
|
||||||
|
title.title_status,
|
||||||
|
title.rating,
|
||||||
|
title.rating_count,
|
||||||
|
title.release_year,
|
||||||
|
title.release_season,
|
||||||
|
title.season,
|
||||||
|
title.episodes_aired,
|
||||||
|
title.episodes_all,
|
||||||
|
episodes_len_json,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
row = await cur.fetchone()
|
||||||
|
return row["id"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def insert_title_if_not_exists(
|
||||||
|
conn: Conn,
|
||||||
|
title: CanonicalTitle,
|
||||||
|
studio_id: Optional[int],
|
||||||
|
poster_id: Optional[int],
|
||||||
|
) -> int:
|
||||||
|
existing_id = await find_title_id_by_name_and_year(
|
||||||
|
conn,
|
||||||
|
title.title_names,
|
||||||
|
title.release_year,
|
||||||
|
)
|
||||||
|
if existing_id is not None:
|
||||||
|
return existing_id
|
||||||
|
|
||||||
|
return await insert_title(conn, title, studio_id, poster_id)
|
||||||
35
modules/anime_etl/images/downloader.py
Normal file
35
modules/anime_etl/images/downloader.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# anime_etl/images/downloader.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
IMAGE_SERVICE_URL: Final[str] = os.getenv(
|
||||||
|
"NYANIMEDB_IMAGE_SERVICE_URL",
|
||||||
|
"http://127.0.0.1:8000"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def ensure_image_downloaded(url: str, subdir: str = "posters") -> str:
|
||||||
|
"""
|
||||||
|
Просит image-service скачать картинку по URL и сохранить её у себя.
|
||||||
|
|
||||||
|
Возвращает относительный путь (subdir/ab/cd/<sha1>.ext),
|
||||||
|
который можно писать в images.image_path.
|
||||||
|
"""
|
||||||
|
async with httpx.AsyncClient(timeout=20.0) as client:
|
||||||
|
resp = await client.post(
|
||||||
|
f"{IMAGE_SERVICE_URL}/download-by-url",
|
||||||
|
json={"url": url, "subdir": subdir},
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
# ожидаем {"path": "..."}
|
||||||
|
path = data["path"]
|
||||||
|
if not isinstance(path, str):
|
||||||
|
raise RuntimeError(f"Invalid response from image service: {data!r}")
|
||||||
|
|
||||||
|
return path
|
||||||
38
modules/anime_etl/jikan_studio_enricher.py
Normal file
38
modules/anime_etl/jikan_studio_enricher.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from models import Studio
|
||||||
|
from sources.jikan_async_client import search_producer, fetch_producer_full
|
||||||
|
|
||||||
|
|
||||||
|
async def enrich_studio_with_jikan_desc(studio: Studio) -> Studio:
|
||||||
|
"""
|
||||||
|
Если у студии нет description — ищем её в Jikan и подтягиваем about.
|
||||||
|
|
||||||
|
Ничего не ломает:
|
||||||
|
- если не нашли / нет about — возвращаем студию как есть
|
||||||
|
- poster/id не трогаем
|
||||||
|
"""
|
||||||
|
if not studio or studio.description:
|
||||||
|
return studio
|
||||||
|
|
||||||
|
matches = await search_producer(studio.name, limit=1)
|
||||||
|
if not matches:
|
||||||
|
return studio
|
||||||
|
|
||||||
|
mal_id = matches[0].get("mal_id")
|
||||||
|
if not isinstance(mal_id, int):
|
||||||
|
return studio
|
||||||
|
|
||||||
|
full = await fetch_producer_full(mal_id)
|
||||||
|
if not full:
|
||||||
|
return studio
|
||||||
|
|
||||||
|
about = full.get("about")
|
||||||
|
if not isinstance(about, str) or not about.strip():
|
||||||
|
return studio
|
||||||
|
|
||||||
|
# лёгкая нормализация: убираем лишние переносы/пробелы
|
||||||
|
studio.description = " ".join(about.split())
|
||||||
|
return studio
|
||||||
0
modules/anime_etl/mappers/__init__.py
Normal file
0
modules/anime_etl/mappers/__init__.py
Normal file
35
modules/anime_etl/mappers/anilist_filters.py
Normal file
35
modules/anime_etl/mappers/anilist_filters.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
SEASON = {
|
||||||
|
"winter": "WINTER",
|
||||||
|
"spring": "SPRING",
|
||||||
|
"summer": "SUMMER",
|
||||||
|
"fall": "FALL",
|
||||||
|
}
|
||||||
|
|
||||||
|
FORMAT = {
|
||||||
|
"tv": "TV",
|
||||||
|
"movie": "MOVIE",
|
||||||
|
"ova": "OVA",
|
||||||
|
"ona": "ONA",
|
||||||
|
"special": "SPECIAL",
|
||||||
|
"music": "MUSIC",
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_anilist_filters(local: dict) -> dict:
|
||||||
|
"""Наши фильтры → AniList GraphQL variables."""
|
||||||
|
q = local.get("query")
|
||||||
|
year = local.get("year")
|
||||||
|
season = SEASON.get(local.get("season"))
|
||||||
|
fmt = FORMAT.get(local.get("type"))
|
||||||
|
limit = local.get("limit", 10)
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"page": 1,
|
||||||
|
"perPage": limit,
|
||||||
|
}
|
||||||
|
|
||||||
|
if q: variables["search"] = q
|
||||||
|
if year: variables["seasonYear"] = year
|
||||||
|
if season: variables["season"] = season
|
||||||
|
if fmt: variables["format"] = fmt
|
||||||
|
|
||||||
|
return variables
|
||||||
82
modules/anime_etl/models.py
Normal file
82
modules/anime_etl/models.py
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
# anime_etl/models.py
|
||||||
|
from __future__ import annotations
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Source(str, Enum):
|
||||||
|
ANILIST = "anilist"
|
||||||
|
JIKAN = "jikan"
|
||||||
|
SHIKIMORI = "shikimori"
|
||||||
|
|
||||||
|
|
||||||
|
class Studio(BaseModel):
|
||||||
|
id: Optional[int] = None
|
||||||
|
name: str
|
||||||
|
poster: Optional["Image"] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Image(BaseModel):
|
||||||
|
id: Optional[int] = None
|
||||||
|
storage_type: Optional[str] = None # для БД: storage_type_t
|
||||||
|
image_path: str # для внешнего постера – URL
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(BaseModel):
|
||||||
|
# локализованный тег, как в Tag.yaml: { "en": "Shojo", "ru": "Сёдзё", ... }
|
||||||
|
names: Dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
|
class SourceTitle(BaseModel):
|
||||||
|
"""
|
||||||
|
Нормализованная инфа из одного источника (AniList/Jikan/...).
|
||||||
|
"""
|
||||||
|
source: Source
|
||||||
|
external_id: str
|
||||||
|
|
||||||
|
title_names: Dict[str, List[str]]
|
||||||
|
studio: Optional[Studio] = None
|
||||||
|
tags: List[Tag] = []
|
||||||
|
|
||||||
|
poster: Optional[Image] = None
|
||||||
|
|
||||||
|
title_status: Optional[str] = None
|
||||||
|
rating: Optional[float] = None
|
||||||
|
rating_count: Optional[int] = None
|
||||||
|
|
||||||
|
release_year: Optional[int] = None
|
||||||
|
release_season: Optional[str] = None
|
||||||
|
|
||||||
|
season: Optional[int] = None
|
||||||
|
|
||||||
|
episodes_aired: Optional[int] = None
|
||||||
|
episodes_all: Optional[int] = None
|
||||||
|
episodes_len: Optional[Dict[str, float]] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CanonicalTitle(BaseModel):
|
||||||
|
"""
|
||||||
|
То, что пойдёт в нашу БД + будет соответствовать Title.yaml.
|
||||||
|
"""
|
||||||
|
id: Optional[int] = None
|
||||||
|
|
||||||
|
title_names: Dict[str, List[str]]
|
||||||
|
|
||||||
|
studio: Optional[Studio] = None
|
||||||
|
tags: List[Tag] = []
|
||||||
|
|
||||||
|
poster: Optional[Image] = None
|
||||||
|
|
||||||
|
title_status: Optional[str] = None
|
||||||
|
rating: Optional[float] = None
|
||||||
|
rating_count: Optional[int] = None
|
||||||
|
|
||||||
|
release_year: Optional[int] = None
|
||||||
|
release_season: Optional[str] = None
|
||||||
|
|
||||||
|
season: Optional[int] = None
|
||||||
|
episodes_aired: Optional[int] = None
|
||||||
|
episodes_all: Optional[int] = None
|
||||||
|
episodes_len: Optional[Dict[str, float]] = None
|
||||||
0
modules/anime_etl/normalizers/__init__.py
Normal file
0
modules/anime_etl/normalizers/__init__.py
Normal file
184
modules/anime_etl/normalizers/anilist_normalizer.py
Normal file
184
modules/anime_etl/normalizers/anilist_normalizer.py
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
# anime_etl/anilist_normalizer.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from models import Source, SourceTitle, Studio, Image, Tag
|
||||||
|
from utils.season_resolver import resolve_season_from_media
|
||||||
|
|
||||||
|
STATUS_MAP: Dict[str, str] = {
|
||||||
|
"FINISHED": "finished",
|
||||||
|
"RELEASING": "ongoing",
|
||||||
|
"NOT_YET_RELEASED": "planned",
|
||||||
|
"CANCELLED": "planned",
|
||||||
|
"HIATUS": "ongoing",
|
||||||
|
}
|
||||||
|
|
||||||
|
SEASON_MAP: Dict[str, str] = {
|
||||||
|
"WINTER": "winter",
|
||||||
|
"SPRING": "spring",
|
||||||
|
"SUMMER": "summer",
|
||||||
|
"FALL": "fall",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _title_names(media: Dict[str, Any]) -> Dict[str, List[str]]:
|
||||||
|
t = media.get("title") or {}
|
||||||
|
native = t.get("native")
|
||||||
|
english = t.get("english")
|
||||||
|
romaji = t.get("romaji")
|
||||||
|
|
||||||
|
res: Dict[str, List[str]] = {}
|
||||||
|
if native:
|
||||||
|
res.setdefault("ja", []).append(native)
|
||||||
|
if english:
|
||||||
|
res.setdefault("en", []).append(english)
|
||||||
|
if romaji:
|
||||||
|
res.setdefault("romaji", []).append(romaji)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def _studio(media: Dict[str, Any]) -> Optional[Studio]:
|
||||||
|
studios_nodes = (media.get("studios") or {}).get("nodes") or []
|
||||||
|
if not studios_nodes:
|
||||||
|
return None
|
||||||
|
name = studios_nodes[0].get("name")
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
return Studio(id=None, name=name, poster=None, description=None)
|
||||||
|
|
||||||
|
|
||||||
|
def _tags(media: Dict[str, Any]) -> List[Tag]:
|
||||||
|
genres = media.get("genres") or []
|
||||||
|
res: List[Tag] = []
|
||||||
|
for g in genres:
|
||||||
|
if g:
|
||||||
|
res.append(Tag(names={"en": g}))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def _poster(media: Dict[str, Any]) -> Optional[Image]:
|
||||||
|
cover = media.get("coverImage") or {}
|
||||||
|
url = cover.get("extraLarge") or cover.get("large")
|
||||||
|
if not url:
|
||||||
|
return None
|
||||||
|
return Image(id=None, storage_type=None, image_path=url)
|
||||||
|
|
||||||
|
|
||||||
|
def _status(media: Dict[str, Any]) -> Optional[str]:
|
||||||
|
raw = media.get("status")
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
return STATUS_MAP.get(raw)
|
||||||
|
|
||||||
|
|
||||||
|
def _rating(media: Dict[str, Any]) -> Optional[float]:
|
||||||
|
avg = media.get("averageScore")
|
||||||
|
if avg is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(avg) / 10.0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _rating_count(media: Dict[str, Any]) -> Optional[int]:
|
||||||
|
pop = media.get("popularity")
|
||||||
|
if pop is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(pop)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _year_and_season(media: Dict[str, Any]) -> tuple[Optional[int], Optional[str]]:
|
||||||
|
year = media.get("seasonYear")
|
||||||
|
raw_season = media.get("season")
|
||||||
|
release_year = year if isinstance(year, int) else None
|
||||||
|
release_season = None
|
||||||
|
if isinstance(raw_season, str):
|
||||||
|
release_season = SEASON_MAP.get(raw_season.upper())
|
||||||
|
return release_year, release_season
|
||||||
|
|
||||||
|
|
||||||
|
def _episodes(media: Dict[str, Any]) -> tuple[Optional[int], Optional[int]]:
|
||||||
|
episodes_all = media.get("episodes")
|
||||||
|
if not isinstance(episodes_all, int):
|
||||||
|
episodes_all = None
|
||||||
|
|
||||||
|
next_ep = media.get("nextAiringEpisode") or {}
|
||||||
|
ep_num = next_ep.get("episode") if isinstance(next_ep, dict) else None
|
||||||
|
if not isinstance(ep_num, int):
|
||||||
|
ep_num = None
|
||||||
|
|
||||||
|
# базовая логика
|
||||||
|
if ep_num is not None:
|
||||||
|
episodes_aired = ep_num - 1
|
||||||
|
else:
|
||||||
|
episodes_aired = episodes_all
|
||||||
|
|
||||||
|
# приведение к инварианту БД:
|
||||||
|
# либо обе NULL, либо обе заданы и episodes_aired <= episodes_all
|
||||||
|
if episodes_aired is None and episodes_all is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
if episodes_all is None and episodes_aired is not None:
|
||||||
|
episodes_all = episodes_aired
|
||||||
|
|
||||||
|
if episodes_aired is None and episodes_all is not None:
|
||||||
|
episodes_aired = episodes_all
|
||||||
|
|
||||||
|
if (
|
||||||
|
episodes_aired is not None
|
||||||
|
and episodes_all is not None
|
||||||
|
and episodes_aired > episodes_all
|
||||||
|
):
|
||||||
|
episodes_aired = episodes_all
|
||||||
|
|
||||||
|
return episodes_aired, episodes_all
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _episodes_len(media: Dict[str, Any]) -> Optional[Dict[str, float]]:
|
||||||
|
duration = media.get("duration")
|
||||||
|
if duration is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return {"default": float(duration)}
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_media(media: Dict[str, Any]) -> SourceTitle:
|
||||||
|
"""AniList Media JSON -> наш SourceTitle."""
|
||||||
|
title_names = _title_names(media)
|
||||||
|
studio = _studio(media)
|
||||||
|
tags = _tags(media)
|
||||||
|
poster = _poster(media)
|
||||||
|
title_status = _status(media)
|
||||||
|
rating = _rating(media)
|
||||||
|
rating_count = _rating_count(media)
|
||||||
|
release_year, release_season = _year_and_season(media)
|
||||||
|
episodes_aired, episodes_all = _episodes(media)
|
||||||
|
episodes_len = _episodes_len(media)
|
||||||
|
|
||||||
|
season = resolve_season_from_media(media)
|
||||||
|
|
||||||
|
return SourceTitle(
|
||||||
|
source=Source.ANILIST,
|
||||||
|
external_id=str(media["id"]),
|
||||||
|
title_names=title_names,
|
||||||
|
studio=studio,
|
||||||
|
tags=tags,
|
||||||
|
poster=poster,
|
||||||
|
title_status=title_status,
|
||||||
|
rating=rating,
|
||||||
|
rating_count=rating_count,
|
||||||
|
release_year=release_year,
|
||||||
|
release_season=release_season,
|
||||||
|
season=season,
|
||||||
|
episodes_aired=episodes_aired,
|
||||||
|
episodes_all=episodes_all,
|
||||||
|
episodes_len=episodes_len,
|
||||||
|
)
|
||||||
13
modules/anime_etl/pyproject.toml
Normal file
13
modules/anime_etl/pyproject.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[project]
|
||||||
|
name = "anime-etl"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"aio-pika>=9.5.8",
|
||||||
|
"httpx>=0.28.1",
|
||||||
|
"psycopg[binary]>=3.3.1",
|
||||||
|
"pydantic>=2.12.5",
|
||||||
|
"python-dotenv>=1.2.1",
|
||||||
|
]
|
||||||
147
modules/anime_etl/rabbit_worker.py
Normal file
147
modules/anime_etl/rabbit_worker.py
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
import aio_pika
|
||||||
|
from aio_pika.exceptions import AMQPConnectionError
|
||||||
|
import psycopg
|
||||||
|
from psycopg.rows import dict_row
|
||||||
|
|
||||||
|
from services.anilist_importer import AniListImporter
|
||||||
|
|
||||||
|
PG_DSN = os.getenv("NYANIMEDB_PG_DSN") or os.getenv("DATABASE_URL")
|
||||||
|
RMQ_URL = os.getenv("NYANIMEDB_RMQ_URL") or os.getenv("RABBITMQ_URL") or "amqp://guest:guest@rabbitmq:5672/"
|
||||||
|
RPC_QUEUE_NAME = os.getenv("NYANIMEDB_IMPORT_RPC_QUEUE", "anime_import_rpc")
|
||||||
|
|
||||||
|
|
||||||
|
def rmq_request_to_filters(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
filters: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
name = payload.get("name")
|
||||||
|
if isinstance(name, str) and name.strip():
|
||||||
|
filters["query"] = name.strip()
|
||||||
|
|
||||||
|
year = payload.get("year")
|
||||||
|
if isinstance(year, int) and year > 0:
|
||||||
|
filters["year"] = year
|
||||||
|
|
||||||
|
season = payload.get("season")
|
||||||
|
if isinstance(season, str) and season:
|
||||||
|
filters["season"] = season.lower()
|
||||||
|
|
||||||
|
filters.setdefault("limit", 10)
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
|
def create_handler(channel: aio_pika.Channel):
|
||||||
|
async def handle_message(message: aio_pika.IncomingMessage) -> None:
|
||||||
|
async with message.process():
|
||||||
|
try:
|
||||||
|
payload = json.loads(message.body.decode("utf-8"))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
filters = rmq_request_to_filters(payload)
|
||||||
|
timestamp = payload.get("timestamp")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with await psycopg.AsyncConnection.connect(
|
||||||
|
PG_DSN,
|
||||||
|
row_factory=dict_row,
|
||||||
|
) as conn:
|
||||||
|
importer = AniListImporter()
|
||||||
|
titles = await importer.import_by_filters_in_tx(conn, filters)
|
||||||
|
|
||||||
|
response: dict[str, Any] = {
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"ok": True,
|
||||||
|
"titles": titles,
|
||||||
|
"error": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
response = {
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"ok": False,
|
||||||
|
"titles": [],
|
||||||
|
"error": {
|
||||||
|
"code": "import_failed",
|
||||||
|
"message": str(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
body = json.dumps(response).encode("utf-8")
|
||||||
|
|
||||||
|
if message.reply_to:
|
||||||
|
await channel.default_exchange.publish(
|
||||||
|
aio_pika.Message(
|
||||||
|
body=body,
|
||||||
|
content_type="application/json",
|
||||||
|
correlation_id=message.correlation_id,
|
||||||
|
),
|
||||||
|
routing_key=message.reply_to,
|
||||||
|
)
|
||||||
|
|
||||||
|
return handle_message
|
||||||
|
|
||||||
|
|
||||||
|
async def connect_rmq_with_retry(
|
||||||
|
url: str,
|
||||||
|
retries: int = 20,
|
||||||
|
delay: float = 3.0,
|
||||||
|
) -> aio_pika.RobustConnection:
|
||||||
|
last_exc: Exception | None = None
|
||||||
|
|
||||||
|
for attempt in range(1, retries + 1):
|
||||||
|
try:
|
||||||
|
print(f"[worker] Connecting to RabbitMQ ({attempt}/{retries}) {url}", flush=True)
|
||||||
|
conn = await aio_pika.connect_robust(url)
|
||||||
|
print("[worker] Connected to RabbitMQ", flush=True)
|
||||||
|
return conn
|
||||||
|
except AMQPConnectionError as e:
|
||||||
|
last_exc = e
|
||||||
|
print(f"[worker] RabbitMQ connection failed: {e!r}, retry in {delay}s", flush=True)
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
|
print("[worker] Failed to connect to RabbitMQ after retries", file=sys.stderr, flush=True)
|
||||||
|
if last_exc:
|
||||||
|
raise last_exc
|
||||||
|
raise RuntimeError("Failed to connect to RabbitMQ")
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
if not PG_DSN:
|
||||||
|
raise RuntimeError("PG_DSN is not set (NYANIMEDB_PG_DSN / DATABASE_URL)")
|
||||||
|
|
||||||
|
print(f"[worker] Starting. PG_DSN={PG_DSN!r}, RMQ_URL={RMQ_URL!r}, queue={RPC_QUEUE_NAME!r}", flush=True)
|
||||||
|
|
||||||
|
connection = await connect_rmq_with_retry(RMQ_URL)
|
||||||
|
channel = await connection.channel()
|
||||||
|
|
||||||
|
queue = await channel.declare_queue(
|
||||||
|
RPC_QUEUE_NAME,
|
||||||
|
durable=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
handler = create_handler(channel)
|
||||||
|
await queue.consume(handler)
|
||||||
|
|
||||||
|
print(f"[*] Waiting for messages in '{RPC_QUEUE_NAME}'. Ctrl+C to exit.", flush=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await asyncio.Future() # run forever
|
||||||
|
finally:
|
||||||
|
await connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||||
|
asyncio.run(main())
|
||||||
122
modules/anime_etl/rate_limiter.py
Normal file
122
modules/anime_etl/rate_limiter.py
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Awaitable, Callable, TypeVar, Optional
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
NowFn = Callable[[], float]
|
||||||
|
SleepFn = Callable[[float], Awaitable[None]]
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimiter(ABC):
|
||||||
|
"""
|
||||||
|
Базовый интерфейс rate-limiter'а.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def acquire(self) -> None:
|
||||||
|
"""
|
||||||
|
Дождаться слота для выполнения запроса.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class TokenBucketRateLimiter(RateLimiter):
|
||||||
|
"""
|
||||||
|
Token-bucket rate limiter.
|
||||||
|
|
||||||
|
rate — сколько токенов выдаём за период `per` секунд.
|
||||||
|
per — длина окна в секундах.
|
||||||
|
capacity — максимальное количество токенов в корзине (burst). По умолчанию = rate.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
limiter = TokenBucketRateLimiter(rate=30, per=60.0)
|
||||||
|
await limiter.acquire() # перед каждым запросом к AniList
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
rate: int,
|
||||||
|
per: float,
|
||||||
|
*,
|
||||||
|
capacity: Optional[int] = None,
|
||||||
|
now_fn: NowFn | None = None,
|
||||||
|
sleep_fn: SleepFn | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.rate = float(rate)
|
||||||
|
self.per = float(per)
|
||||||
|
self.capacity = float(capacity if capacity is not None else rate)
|
||||||
|
|
||||||
|
self._now: NowFn = now_fn or time.monotonic
|
||||||
|
self._sleep: SleepFn = sleep_fn or asyncio.sleep
|
||||||
|
|
||||||
|
# начальное состояние: полный бак
|
||||||
|
self._tokens: float = self.capacity
|
||||||
|
self._updated_at: float = self._now()
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
|
async def acquire(self) -> None:
|
||||||
|
"""
|
||||||
|
Подождать, пока не будет доступен хотя бы один токен.
|
||||||
|
Важно: ожидание (sleep) происходит ВНЕ lock'а, чтобы несколько корутин
|
||||||
|
могли "делить" ожидание.
|
||||||
|
"""
|
||||||
|
refill_rate_per_second = self.rate / self.per
|
||||||
|
|
||||||
|
while True:
|
||||||
|
async with self._lock:
|
||||||
|
now = self._now()
|
||||||
|
elapsed = now - self._updated_at
|
||||||
|
|
||||||
|
if elapsed > 0:
|
||||||
|
self._tokens = min(
|
||||||
|
self.capacity,
|
||||||
|
self._tokens + elapsed * refill_rate_per_second,
|
||||||
|
)
|
||||||
|
self._updated_at = now
|
||||||
|
|
||||||
|
if self._tokens >= 1.0:
|
||||||
|
self._tokens -= 1.0
|
||||||
|
return
|
||||||
|
|
||||||
|
# токенов нет — считаем, сколько нужно ждать
|
||||||
|
missing = 1.0 - self._tokens
|
||||||
|
wait_for = missing / refill_rate_per_second
|
||||||
|
if wait_for <= 0:
|
||||||
|
wait_for = 0.01
|
||||||
|
|
||||||
|
# спим уже без lock'а, чтобы другие могли проверить состояние
|
||||||
|
await self._sleep(wait_for)
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_with_rate_limiter(
|
||||||
|
limiter: RateLimiter,
|
||||||
|
) -> Callable[[Callable[..., Awaitable[T]]], Callable[..., Awaitable[T]]]:
|
||||||
|
"""
|
||||||
|
Декоратор для функций, которые делают запросы к внешнему API.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
limiter = TokenBucketRateLimiter(rate=30, per=60.0)
|
||||||
|
|
||||||
|
@wrap_with_rate_limiter(limiter)
|
||||||
|
async def fetch_something(...):
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
|
||||||
|
async def wrapper(*args, **kwargs) -> T:
|
||||||
|
await limiter.acquire()
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
# Глобальные инстансы per-process для источников
|
||||||
|
ANILIST_RATE_LIMITER = TokenBucketRateLimiter(rate=30, per=60.0)
|
||||||
|
SHIKIMORI_RATE_LIMITER = TokenBucketRateLimiter(rate=90, per=60.0)
|
||||||
|
JIKAN_RATE_LIMITER = TokenBucketRateLimiter(rate=30, per=60.0)
|
||||||
93
modules/anime_etl/services/anilist_importer.py
Normal file
93
modules/anime_etl/services/anilist_importer.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# anime_etl/services/anilist_importer.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
from psycopg.rows import dict_row
|
||||||
|
|
||||||
|
from sources.anilist_source import AniListSource
|
||||||
|
from canonicalizer import source_title_to_canonical
|
||||||
|
from db.repository import (
|
||||||
|
get_or_create_studio,
|
||||||
|
get_or_create_image,
|
||||||
|
insert_title_if_not_exists,
|
||||||
|
)
|
||||||
|
from models import CanonicalTitle
|
||||||
|
from jikan_studio_enricher import enrich_studio_with_jikan_desc
|
||||||
|
|
||||||
|
|
||||||
|
Conn = psycopg.AsyncConnection
|
||||||
|
|
||||||
|
|
||||||
|
class AniListImporter:
|
||||||
|
def __init__(self, source: AniListSource | None = None) -> None:
|
||||||
|
self._source = source or AniListSource()
|
||||||
|
|
||||||
|
async def import_by_filters_in_tx(
|
||||||
|
self,
|
||||||
|
conn: Conn,
|
||||||
|
filters: Dict[str, Any],
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Выполнить импорт в рамках одной транзакции:
|
||||||
|
- поиск в AniList
|
||||||
|
- канонизация
|
||||||
|
- обогащение студии (Jikan)
|
||||||
|
- get_or_create_studio (+ illust_id)
|
||||||
|
- скачивание постера тайтла -> images
|
||||||
|
- insert_title_if_not_exists
|
||||||
|
"""
|
||||||
|
async with conn.transaction():
|
||||||
|
return await self._import_by_filters(conn, filters)
|
||||||
|
|
||||||
|
async def _import_by_filters(
|
||||||
|
self,
|
||||||
|
conn: Conn,
|
||||||
|
filters: Dict[str, Any],
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
source_titles = await self._source.search(filters)
|
||||||
|
|
||||||
|
results: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
for st in source_titles:
|
||||||
|
canonical: CanonicalTitle = source_title_to_canonical(st)
|
||||||
|
|
||||||
|
# 1) обогатить студию описанием из Jikan (если есть студия и ещё нет description)
|
||||||
|
if canonical.studio is None:
|
||||||
|
continue
|
||||||
|
canonical.studio = await enrich_studio_with_jikan_desc(canonical.studio)
|
||||||
|
|
||||||
|
# 2) создать/обновить студию (studio_name, illust_id, studio_desc)
|
||||||
|
studio_id = await get_or_create_studio(conn, canonical.studio)
|
||||||
|
|
||||||
|
# 3) скачать постер тайтла и создать запись в images
|
||||||
|
poster_id = await get_or_create_image(conn, canonical.poster, subdir="posters")
|
||||||
|
|
||||||
|
# 4) создать тайтл, если его ещё нет (с учётом studio_id и poster_id)
|
||||||
|
title_id = await insert_title_if_not_exists(conn, canonical, studio_id, poster_id)
|
||||||
|
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"id": title_id,
|
||||||
|
"title_names": canonical.title_names,
|
||||||
|
"release_year": canonical.release_year,
|
||||||
|
"release_season": canonical.release_season,
|
||||||
|
"season": canonical.season,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
async def import_from_anilist(
|
||||||
|
dsn: str,
|
||||||
|
filters: Dict[str, Any],
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Открывает подключение к БД, делает транзакцию и импорт.
|
||||||
|
"""
|
||||||
|
importer = AniListImporter()
|
||||||
|
|
||||||
|
async with await psycopg.AsyncConnection.connect(dsn, row_factory=dict_row) as conn:
|
||||||
|
return await importer.import_by_filters_in_tx(conn, filters)
|
||||||
0
modules/anime_etl/sources/__init__.py
Normal file
0
modules/anime_etl/sources/__init__.py
Normal file
47
modules/anime_etl/sources/anilist_async_client.py
Normal file
47
modules/anime_etl/sources/anilist_async_client.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import httpx
|
||||||
|
import asyncio
|
||||||
|
from rate_limiter import ANILIST_RATE_LIMITER
|
||||||
|
|
||||||
|
URL = "https://graphql.anilist.co"
|
||||||
|
|
||||||
|
QUERY = """
|
||||||
|
query ($search: String, $season: MediaSeason, $seasonYear: Int, $format: MediaFormat, $page: Int, $perPage: Int) {
|
||||||
|
Page(page: $page, perPage: $perPage) {
|
||||||
|
media(search: $search, type: ANIME, season: $season, seasonYear: $seasonYear, format: $format) {
|
||||||
|
id
|
||||||
|
title { romaji english native }
|
||||||
|
status
|
||||||
|
season
|
||||||
|
seasonYear
|
||||||
|
format
|
||||||
|
episodes
|
||||||
|
duration
|
||||||
|
popularity
|
||||||
|
averageScore
|
||||||
|
coverImage { extraLarge large }
|
||||||
|
genres
|
||||||
|
studios(isMain: true) { nodes { id name } }
|
||||||
|
nextAiringEpisode { episode }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT = httpx.AsyncClient(timeout=15.0)
|
||||||
|
|
||||||
|
async def _post(payload: dict) -> dict:
|
||||||
|
for i in range(5):
|
||||||
|
await ANILIST_RATE_LIMITER.acquire()
|
||||||
|
try:
|
||||||
|
r = await CLIENT.post(URL, json=payload)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
except Exception:
|
||||||
|
await asyncio.sleep(1 * 2**i)
|
||||||
|
raise RuntimeError("AniList unreachable")
|
||||||
|
|
||||||
|
async def search_raw(filters: dict) -> list[dict]:
|
||||||
|
payload = {"query": QUERY, "variables": filters}
|
||||||
|
data = await _post(payload)
|
||||||
|
return data.get("data", {}).get("Page", {}).get("media") or []
|
||||||
35
modules/anime_etl/sources/anilist_source.py
Normal file
35
modules/anime_etl/sources/anilist_source.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
from mappers.anilist_filters import to_anilist_filters
|
||||||
|
from sources.anilist_async_client import search_raw
|
||||||
|
from normalizers.anilist_normalizer import normalize_media
|
||||||
|
import asyncio
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
|
||||||
|
class AniListSource:
|
||||||
|
async def search(self, local_filters: dict) -> list:
|
||||||
|
ani_filters = to_anilist_filters(local_filters)
|
||||||
|
raw_list = await search_raw(ani_filters)
|
||||||
|
return [normalize_media(r) for r in raw_list]
|
||||||
|
|
||||||
|
|
||||||
|
async def _demo() -> None:
|
||||||
|
src = AniListSource()
|
||||||
|
filters = {
|
||||||
|
"query": "monogatari",
|
||||||
|
# "year": 2017,
|
||||||
|
# "season": "winter",
|
||||||
|
# "type": "tv",
|
||||||
|
# "limit": 5,
|
||||||
|
}
|
||||||
|
print("Запускаю поиск с фильтрами:", filters)
|
||||||
|
titles = await src.search(filters)
|
||||||
|
print("Найдено тайтлов:", len(titles))
|
||||||
|
|
||||||
|
for t in titles:
|
||||||
|
# t.title_names — dict[str, list[str]]
|
||||||
|
# en = (t.title_names.get("en") or [""])[0]
|
||||||
|
# print("-", en, "|", t.release_year, t.release_season)
|
||||||
|
pprint.pprint(t)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(_demo())
|
||||||
54
modules/anime_etl/sources/jikan_async_client.py
Normal file
54
modules/anime_etl/sources/jikan_async_client.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import asyncio
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from rate_limiter import JIKAN_RATE_LIMITER
|
||||||
|
|
||||||
|
BASE_URL = "https://api.jikan.moe/v4"
|
||||||
|
CLIENT = httpx.AsyncClient(timeout=15.0)
|
||||||
|
|
||||||
|
|
||||||
|
async def _get(path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Обёртка над GET с ретраями и rate-limit'ом.
|
||||||
|
"""
|
||||||
|
url = f"{BASE_URL}{path}"
|
||||||
|
last_exc: Exception | None = None
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
await JIKAN_RATE_LIMITER.acquire()
|
||||||
|
try:
|
||||||
|
r = await CLIENT.get(url, params=params)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()
|
||||||
|
except Exception as exc:
|
||||||
|
last_exc = exc
|
||||||
|
await asyncio.sleep(1 * 2**i)
|
||||||
|
|
||||||
|
raise RuntimeError(f"Jikan unreachable: {last_exc!r}")
|
||||||
|
|
||||||
|
|
||||||
|
async def search_producer(name: str, limit: int = 1) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Поиск студии/продюсера по имени.
|
||||||
|
|
||||||
|
Возвращает список элементов из data[]:
|
||||||
|
[{ "mal_id": int, "name": str, ... }, ...]
|
||||||
|
"""
|
||||||
|
if not name:
|
||||||
|
return []
|
||||||
|
|
||||||
|
data = await _get("/producers", {"q": name, "limit": limit})
|
||||||
|
return data.get("data") or []
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_producer_full(mal_id: int) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Полная инфа по producer'у (есть поле about).
|
||||||
|
"""
|
||||||
|
if not isinstance(mal_id, int):
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = await _get(f"/producers/{mal_id}/full")
|
||||||
|
return data.get("data") or None
|
||||||
0
modules/anime_etl/utils/__init__.py
Normal file
0
modules/anime_etl/utils/__init__.py
Normal file
89
modules/anime_etl/utils/season_resolver.py
Normal file
89
modules/anime_etl/utils/season_resolver.py
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# anime_etl/utils/season_resolver.py
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
_ROMAN = {
|
||||||
|
"I": 1, "II": 2, "III": 3, "IV": 4,
|
||||||
|
"V": 5, "VI": 6, "VII": 7, "VIII": 8,
|
||||||
|
"IX": 9, "X": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _roman_to_int(token: str) -> Optional[int]:
|
||||||
|
return _ROMAN.get(token.upper())
|
||||||
|
|
||||||
|
|
||||||
|
# Паттерны типа:
|
||||||
|
# - "Season 2"
|
||||||
|
# - "2nd Season"
|
||||||
|
# - "S3"
|
||||||
|
# - "III"
|
||||||
|
_SEASON_PATTERNS = [
|
||||||
|
# "Season 2"
|
||||||
|
re.compile(r"\bseason\s*(\d{1,2})\b", re.IGNORECASE),
|
||||||
|
# "2nd Season"
|
||||||
|
re.compile(r"\b(\d{1,2})(?:st|nd|rd|th)\s+season\b", re.IGNORECASE),
|
||||||
|
# "S3"
|
||||||
|
re.compile(r"\bs(\d{1,2})\b", re.IGNORECASE),
|
||||||
|
# одиночное число (осторожно, поэтому используем как самый последний fallback)
|
||||||
|
re.compile(r"\b(\d{1,2})\b"),
|
||||||
|
# римские цифры: "III"
|
||||||
|
re.compile(r"\b([IVX]{1,5})\b", re.IGNORECASE),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_season_number_from_title(name: str) -> Optional[int]:
|
||||||
|
name = name.strip()
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for pat in _SEASON_PATTERNS:
|
||||||
|
m = pat.search(name)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
|
||||||
|
token = m.group(1)
|
||||||
|
|
||||||
|
# пробуем римские
|
||||||
|
roman = _roman_to_int(token)
|
||||||
|
if roman is not None:
|
||||||
|
return roman
|
||||||
|
|
||||||
|
# иначе просто число
|
||||||
|
try:
|
||||||
|
return int(token)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_season_from_media(media: dict) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
Определяем номер сезона по данным AniList Media.
|
||||||
|
|
||||||
|
Логика:
|
||||||
|
- Если формат не TV/ONA → считаем, что это не нумерованный сезон (OVA/MOVIE/...)
|
||||||
|
- Берём title.english / romaji / native, пытаемся вытащить номер через regex.
|
||||||
|
"""
|
||||||
|
fmt = media.get("format")
|
||||||
|
if fmt not in ("TV", "ONA"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
title = media.get("title") or {}
|
||||||
|
candidates: list[str] = []
|
||||||
|
|
||||||
|
for key in ("english", "romaji", "native"):
|
||||||
|
v = title.get(key)
|
||||||
|
if isinstance(v, str):
|
||||||
|
candidates.append(v)
|
||||||
|
|
||||||
|
for name in candidates:
|
||||||
|
n = extract_season_number_from_title(name)
|
||||||
|
if n is not None:
|
||||||
|
return n
|
||||||
|
|
||||||
|
return None
|
||||||
606
modules/anime_etl/uv.lock
generated
Normal file
606
modules/anime_etl/uv.lock
generated
Normal file
|
|
@ -0,0 +1,606 @@
|
||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aio-pika"
|
||||||
|
version = "9.5.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aiormq" },
|
||||||
|
{ name = "yarl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c5/73/8d1020683970de5532b3b01732d75c8bf922a6505fcdad1a9c7c6405242a/aio_pika-9.5.8.tar.gz", hash = "sha256:7c36874115f522bbe7486c46d8dd711a4dbedd67c4e8a8c47efe593d01862c62", size = 47408, upload-time = "2025-11-12T10:37:10.215Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/91/513971861d845d28160ecb205ae2cfaf618b16918a9cd4e0b832b5360ce7/aio_pika-9.5.8-py3-none-any.whl", hash = "sha256:f4c6cb8a6c5176d00f39fd7431e9702e638449bc6e86d1769ad7548b2a506a8d", size = 54397, upload-time = "2025-11-12T10:37:08.374Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiormq"
|
||||||
|
version = "6.9.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pamqp" },
|
||||||
|
{ name = "yarl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8e/f6/01bc850db6d9b46ae825e3c373f610b0544e725a1159745a6de99ad0d9f1/aiormq-6.9.2.tar.gz", hash = "sha256:d051d46086079934d3a7157f4d8dcb856b77683c2a94aee9faa165efa6a785d3", size = 30554, upload-time = "2025-10-20T10:49:59.763Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/ec/763b13f148f3760c1562cedb593feaffbae177eeece61af5d0ace7b72a3e/aiormq-6.9.2-py3-none-any.whl", hash = "sha256:ab0f4e88e70f874b0ea344b3c41634d2484b5dc8b17cb6ae0ae7892a172ad003", size = 31829, upload-time = "2025-10-20T10:49:58.547Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anime-etl"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aio-pika" },
|
||||||
|
{ name = "httpx" },
|
||||||
|
{ name = "psycopg", extra = ["binary"] },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "aio-pika", specifier = ">=9.5.8" },
|
||||||
|
{ name = "httpx", specifier = ">=0.28.1" },
|
||||||
|
{ name = "psycopg", extras = ["binary"], specifier = ">=3.3.1" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||||
|
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.11.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2025.11.12"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "h11" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "httpcore" },
|
||||||
|
{ name = "idna" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.11"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multidict"
|
||||||
|
version = "6.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pamqp"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fb/62/35bbd3d3021e008606cd0a9532db7850c65741bbf69ac8a3a0d8cfeb7934/pamqp-3.3.0.tar.gz", hash = "sha256:40b8795bd4efcf2b0f8821c1de83d12ca16d5760f4507836267fd7a02b06763b", size = 30993, upload-time = "2024-01-12T20:37:25.085Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/8d/c1e93296e109a320e508e38118cf7d1fc2a4d1c2ec64de78565b3c445eb5/pamqp-3.3.0-py2.py3-none-any.whl", hash = "sha256:c901a684794157ae39b52cbf700db8c9aae7a470f13528b9d7b4e5f7202f8eb0", size = 33848, upload-time = "2024-01-12T20:37:21.359Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "propcache"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psycopg"
|
||||||
|
version = "3.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||||
|
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/18/ed/3a30e8ef82d4128c76aa9bd6b2a7fe6c16c283811e6655997f5047801b47/psycopg-3.3.1.tar.gz", hash = "sha256:ccfa30b75874eef809c0fbbb176554a2640cc1735a612accc2e2396a92442fc6", size = 165596, upload-time = "2025-12-02T21:09:55.545Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/f3/0b4a4c25a47c2d907afa97674287dab61bc9941c9ac3972a67100e33894d/psycopg-3.3.1-py3-none-any.whl", hash = "sha256:e44d8eae209752efe46318f36dd0fdf5863e928009338d736843bb1084f6435c", size = 212760, upload-time = "2025-12-02T21:02:36.029Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
binary = [
|
||||||
|
{ name = "psycopg-binary", marker = "implementation_name != 'pypy'" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psycopg-binary"
|
||||||
|
version = "3.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/29/12cfc28594aa940f5894da1b2f5368f9163260e3d6b53cf3eb9413d07489/psycopg_binary-3.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f0afb5653614ad4a9a2fa0fa8c593508a18bd319afc26b20a33b883f263bf90", size = 4579837, upload-time = "2025-12-02T21:07:09.264Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/f0/2b4cfca5161af8bb573963d9540f97b191a5dfe1afd02c3183feeade47a2/psycopg_binary-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b84ed483a4d0271be201005c7567161fc6bc884f7ebc08ed9f82083b3a0d1f9e", size = 4658787, upload-time = "2025-12-02T21:07:17.39Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/3b/ade0f141178633b098cc80af7922d13bbfc1014401232785af6e485563a2/psycopg_binary-3.3.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3323652b73305e23cc9b5f4e332b25f00c8cb16f47ef84ee4430b7df38273707", size = 5454896, upload-time = "2025-12-02T21:07:23.918Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/14/90aac9ec57580da90bd6a0986288f0422b0a650f1686e10444b8b579c0f2/psycopg_binary-3.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c64ed9a49e606c764510a1d98270cc42a38527776aa98baf6e8c4e20c5341b96", size = 5132733, upload-time = "2025-12-02T21:07:28.789Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/62/bb2da10712e409ec1579be67a879824ab484989de8ed773309c880b57213/psycopg_binary-3.3.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81eee7d7f5fa9a85778fb854d979ba16f48cec584c17c51117ba94ad9d6a667", size = 6724495, upload-time = "2025-12-02T21:07:34.821Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/34/baf21418e62002c3cc0d35f4431b0b2953c44272e572ccd3b4161ffaa886/psycopg_binary-3.3.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8d0b967eada1831f6e8b652f6868c9fbdf80e397c1f096226fe0d545112f907d", size = 4964978, upload-time = "2025-12-02T21:07:39.34Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/b0/a375f37a852722878e2292f64dba8632d89c9afe0a3e0b9920a6bbcee847/psycopg_binary-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b2c86492e9b41d942bc263e5b961498bd404444b0547e1e2456e8f919599ad14", size = 4493649, upload-time = "2025-12-02T21:07:43.991Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/40/bb4bf3a141a1cbc36abd86867ca352c0807f062d5cb01d3e9141c685975b/psycopg_binary-3.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5468dcdb2717dc764d1e1d9a391b714d28717bc8613e2e5481f261718e4e72c5", size = 4173392, upload-time = "2025-12-02T21:07:48.509Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/e1/178274150e5f0398697e74c0027651c668ca2e2ec57db98c811ba97bf69b/psycopg_binary-3.3.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e3aa33e5553d12b91e23b928e869587289c6c26de58b3b14f70bed06eb767c58", size = 3909241, upload-time = "2025-12-02T21:07:52.85Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/01/220119f18bf8447756b92f5db87a6e723ae1dd1db81ad591393714b71f5e/psycopg_binary-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bbd26acb1ba8416a16256bfd87de9a1427fb2e04f8d79eae3fb64a112ede06f1", size = 4219745, upload-time = "2025-12-02T21:07:57.374Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/87/ece0da8b6befb17bb5ffd64eb28fb5ddd539d2569700f2e3e78e91385434/psycopg_binary-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ce74da70444348135f9b5b9b67121eb9816ef483159bf54083765792c948f249", size = 3537480, upload-time = "2025-12-02T21:08:01.029Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/44/f907c508267bc203082217faf5750274f4c240471f99990db70ed9f4dada/psycopg_binary-3.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fda22ce8530236381ff79a674ebc319f1a224f2e39a44158774e55e1488f89b9", size = 4579070, upload-time = "2025-12-02T21:08:04.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/61/554bd7b9b93aef79f0bd3c4d381d9809ecf38e55ab4eb5984f58a74695f7/psycopg_binary-3.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d2fc5fa6c45c406e43c8cc2787055d487b3ae597d2139125191b37fa04835f01", size = 4657517, upload-time = "2025-12-02T21:08:08.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/1b/c3bc20b72b9b056d1f7a3a131d676c66fe2bc7585f9d9f23d659d8725407/psycopg_binary-3.3.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4d97e4d27cc7ee6938faf7dc9919c452581b4795bd97f3f48582846f24ab81ed", size = 5452087, upload-time = "2025-12-02T21:08:15.144Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/e6/58ae55874b963893f46d68748b28e05c432b0c109f53b40970ba6bf9fe95/psycopg_binary-3.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4fb5a3cf4373a8b06c2feb8f29ff4c69968ba443687dedec9f79ce22ef339ae", size = 5131126, upload-time = "2025-12-02T21:08:23.463Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/28/bfcfb1b7c2907585292aaf61e902cbd00ecff50120178bc3e8268a74c1d6/psycopg_binary-3.3.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4de5298b81423648ae751c789a6adb7f9396bd4de7f402db8f469901d676ebe4", size = 6722913, upload-time = "2025-12-02T21:08:30.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/ba/7f6d3117059d4824854fb4eeafe7cb9fe069a43724f6a36b21aac0cd911d/psycopg_binary-3.3.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a41139976b8546b78ccd776b9f63665c247e209ae384fa6908ea401e9df2c385", size = 4966088, upload-time = "2025-12-02T21:08:34.666Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/18/d1baed589d7254be32eda44262787e397d1845fc7f08a30c38bf9345d361/psycopg_binary-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8ea017e58fa7fd8df1d9058ff0248e28f29312bf150a00114fc0ace8a800bfa", size = 4493332, upload-time = "2025-12-02T21:08:38.935Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/62/57c6ac97cd4d1297115c3840fe09a19ae50b96294e3b8e988385497dc074/psycopg_binary-3.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:447e31350ea6816af03f39e6fc5ccccf18e34dc223a7d82734621048bb3fb9af", size = 4170782, upload-time = "2025-12-02T21:08:43.949Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/27/b2577aad1baaa476cf482fb207850fb3f03467b3e6f3485e8cf360588ea8/psycopg_binary-3.3.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1f97f1814b046c8103b0a46a8c36c28399489716eba70ed38fbae30a27866e2c", size = 3910543, upload-time = "2025-12-02T21:08:48.202Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/89/7ad39e369706beda88827031504d9bb5fed58bf6fe50ec7045bd6750736b/psycopg_binary-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3986f783ff656a0392b81b7ac1e0f88a276a38276a5c93c0b89d4862e309d618", size = 4220070, upload-time = "2025-12-02T21:08:52.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/3a/703c155ab9fb64c874921116fd740c93494e6bd599b1bcd9e4987f5dcfb4/psycopg_binary-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:63e7c689a4249a1303da35df0b813e7cb3b9e2e5eae492a47482ee1a41dc66b2", size = 3540907, upload-time = "2025-12-02T21:08:56.898Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/a5/056d227c85e4b769f2f9a2f2be71d1754492277410e15b2035637bafb92e/psycopg_binary-3.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f7464571c2f4936810cdd7aed9108d3d80c6ea3d668a6e23fe8e9a4f4942d09", size = 4596368, upload-time = "2025-12-02T21:09:01.181Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/a8/5c628a984f3a53ec49d4f56827b6593abc609dc580f879e471fce39835b9/psycopg_binary-3.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4889aa16b2bfd4796a8648087262680d332fe9c6926fd7fd3d85c4f5eb483f01", size = 4675138, upload-time = "2025-12-02T21:09:06.095Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/a8/e3cfd12e1bff144ce4af6e2c1ff72f2562a2853c4020843cb790c014c564/psycopg_binary-3.3.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:87a01fc62483b4cb5c343194d78ef9d7588624ad260fa82b31bf3c08e285a95f", size = 5456120, upload-time = "2025-12-02T21:09:11.385Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/d7/70905001d6865c155b08badc5225d56daafbc064702cc42874f88d21bef8/psycopg_binary-3.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:64b93d8456e17545c53bd23f601488fc508b68c4128133bc97762a00e61e8ab2", size = 5133485, upload-time = "2025-12-02T21:09:15.775Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/6e/0cf90710f154d52163db920d059766ad27b510d90961a7f8f068ae3830f1/psycopg_binary-3.3.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f6b7bc0d230932aed188b9cc44b6fc6d43e2ec1585903d09a2d15095731ee07", size = 6731818, upload-time = "2025-12-02T21:09:25.092Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/0f/c6c81861a8b2be54ff2b57a9eff84e50b3ec97d246dcb23311a25c9f78f0/psycopg_binary-3.3.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d4e3d94c2475fefcb7b93b507fe8fa5c8c61f993c9bece0ffd05906f1dbb47d", size = 4983866, upload-time = "2025-12-02T21:09:31.937Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/88/9804e7749680f35d1eda8d9a979156f3f685a1af3a7c0f124b6a728e3836/psycopg_binary-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7e0998da49b193d35641be04068f061965428a4b5e776065691b7f8c1bbc472e", size = 4516389, upload-time = "2025-12-02T21:09:36.058Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/cf/f136ba0afab5fce9c621883055918cee730671d22f83caa843dae0d728a8/psycopg_binary-3.3.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:cbf1c2fb03b7114808aa251ff6401cad0fa85e78dbecf45d711510797708b256", size = 4192382, upload-time = "2025-12-02T21:09:40.238Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/57/3c089efb7c52b455b3cfcc6d0a367e30f286777a3b291f93abe8eeeaa748/psycopg_binary-3.3.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:18c87c715dda836dcdf1f71c6b71a3d10194f07faf8358d2d42bc637d70137cf", size = 3928661, upload-time = "2025-12-02T21:09:44.658Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/95/a8096c8f61622ae74d55bc4442c5e86b009fe8a07e7e4dd58783fe14fc81/psycopg_binary-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ec0a04b95faf5c6c0af24917883b282f23dc588f0ee565efdd1146ed8129d258", size = 4239169, upload-time = "2025-12-02T21:09:49.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/f0/9603f03eb2f887d47b6554def8f01317069515f4294878011b341759e332/psycopg_binary-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:c0bcb5a5ec01ccc34f884470473b2b9d1730513b7fb7175f741224af6af14182", size = 3642104, upload-time = "2025-12-02T21:09:53.514Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.12.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.41.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-inspection"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2025.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yarl"
|
||||||
|
version = "1.22.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "multidict" },
|
||||||
|
{ name = "propcache" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" },
|
||||||
|
]
|
||||||
|
|
@ -47,28 +47,35 @@ func CheckPassword(password, hash string) (bool, error) {
|
||||||
return argon2id.ComparePasswordAndHash(password, hash)
|
return argon2id.ComparePasswordAndHash(password, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) generateImpersonationToken(userID string, impersonated_by string) (accessToken string, err error) {
|
func (s *Server) generateImpersonationToken(userID string, impersonatedBy string) (string, error) {
|
||||||
accessClaims := jwt.MapClaims{
|
now := time.Now()
|
||||||
"user_id": userID,
|
claims := auth.TokenClaims{
|
||||||
"exp": time.Now().Add(15 * time.Minute).Unix(),
|
ImpID: &impersonatedBy,
|
||||||
"imp_id": impersonated_by,
|
Type: "access",
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
Subject: userID,
|
||||||
|
IssuedAt: jwt.NewNumericDate(now),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(now.Add(15 * time.Minute)),
|
||||||
|
ID: generateJTI(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
at := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return token.SignedString([]byte(s.JwtPrivateKey))
|
||||||
accessToken, err = at.SignedString([]byte(s.JwtPrivateKey))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessToken, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) generateTokens(userID string) (accessToken string, refreshToken string, csrfToken string, err error) {
|
func (s *Server) generateTokens(userID string) (accessToken string, refreshToken string, csrfToken string, err error) {
|
||||||
accessClaims := jwt.MapClaims{
|
now := time.Now()
|
||||||
"user_id": userID,
|
|
||||||
"exp": time.Now().Add(15 * time.Minute).Unix(),
|
// Access token (15 мин)
|
||||||
//TODO: add created_at
|
accessClaims := auth.TokenClaims{
|
||||||
|
Type: "access",
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
Subject: userID,
|
||||||
|
IssuedAt: jwt.NewNumericDate(now),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(now.Add(15 * time.Minute)),
|
||||||
|
ID: generateJTI(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
at := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
|
at := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
|
||||||
accessToken, err = at.SignedString([]byte(s.JwtPrivateKey))
|
accessToken, err = at.SignedString([]byte(s.JwtPrivateKey))
|
||||||
|
|
@ -76,9 +83,15 @@ func (s Server) generateTokens(userID string) (accessToken string, refreshToken
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshClaims := jwt.MapClaims{
|
// Refresh token (7 дней)
|
||||||
"user_id": userID,
|
refreshClaims := auth.TokenClaims{
|
||||||
"exp": time.Now().Add(7 * 24 * time.Hour).Unix(),
|
Type: "refresh",
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
Subject: userID,
|
||||||
|
IssuedAt: jwt.NewNumericDate(now),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(now.Add(7 * 24 * time.Hour)),
|
||||||
|
ID: generateJTI(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
rt := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
|
rt := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
|
||||||
refreshToken, err = rt.SignedString([]byte(s.JwtPrivateKey))
|
refreshToken, err = rt.SignedString([]byte(s.JwtPrivateKey))
|
||||||
|
|
@ -86,6 +99,7 @@ func (s Server) generateTokens(userID string) (accessToken string, refreshToken
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CSRF token
|
||||||
csrfBytes := make([]byte, 32)
|
csrfBytes := make([]byte, 32)
|
||||||
_, err = rand.Read(csrfBytes)
|
_, err = rand.Read(csrfBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -140,7 +154,7 @@ func (s Server) PostSignIn(ctx context.Context, req auth.PostSignInRequestObject
|
||||||
return auth.PostSignIn401Response{}, nil
|
return auth.PostSignIn401Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, csrfToken, err := s.generateTokens(req.Body.Nickname)
|
accessToken, refreshToken, csrfToken, err := s.generateTokens(fmt.Sprintf("%d", user.ID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to generate tokens for user %s: %v", req.Body.Nickname, err)
|
log.Errorf("failed to generate tokens for user %s: %v", req.Body.Nickname, err)
|
||||||
// TODO: return 500
|
// TODO: return 500
|
||||||
|
|
@ -219,56 +233,73 @@ func (s Server) GetImpersonationToken(ctx context.Context, req auth.GetImpersona
|
||||||
return auth.GetImpersonationToken200JSONResponse{AccessToken: accessToken}, nil
|
return auth.GetImpersonationToken200JSONResponse{AccessToken: accessToken}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (s Server) PostAuthRefreshToken(ctx context.Context, req auth.PostAuthRefreshTokenRequestObject) (auth.PostAuthRefreshTokenResponseObject, error) {
|
func (s Server) RefreshTokens(ctx context.Context, req auth.RefreshTokensRequestObject) (auth.RefreshTokensResponseObject, error) {
|
||||||
// valid := false
|
ginCtx, ok := ctx.Value(gin.ContextKey).(*gin.Context)
|
||||||
// var userID *string
|
if !ok {
|
||||||
// var errStr *string
|
log.Print("failed to get gin context")
|
||||||
|
return auth.RefreshTokens500Response{}, fmt.Errorf("failed to get gin.Context from context.Context")
|
||||||
|
}
|
||||||
|
|
||||||
// token, err := jwt.Parse(req.Body.Token, func(t *jwt.Token) (interface{}, error) {
|
rtCookie, err := ginCtx.Request.Cookie("refresh_token")
|
||||||
// if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
if err != nil {
|
||||||
// return nil, fmt.Errorf("unexpected signing method")
|
log.Print("failed to get refresh_token cookie")
|
||||||
// }
|
return auth.RefreshTokens400Response{}, fmt.Errorf("failed to get refresh_token cookie")
|
||||||
// return refreshSecret, nil
|
}
|
||||||
// })
|
|
||||||
|
|
||||||
// if err != nil {
|
refreshToken := rtCookie.Value
|
||||||
// e := err.Error()
|
|
||||||
// errStr = &e
|
|
||||||
// return auth.PostAuthVerifyToken200JSONResponse{
|
|
||||||
// Valid: &valid,
|
|
||||||
// UserId: userID,
|
|
||||||
// Error: errStr,
|
|
||||||
// }, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
token, err := jwt.ParseWithClaims(refreshToken, &auth.TokenClaims{}, func(t *jwt.Token) (interface{}, error) {
|
||||||
// if uid, ok := claims["user_id"].(string); ok {
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
// // Refresh token is valid, generate new tokens
|
return nil, fmt.Errorf("unexpected signing method")
|
||||||
// newAccessToken, newRefreshToken, _ := generateTokens(uid)
|
}
|
||||||
// valid = true
|
return []byte(s.JwtPrivateKey), nil
|
||||||
// userID = &uid
|
})
|
||||||
// return auth.PostAuthVerifyToken200JSONResponse{
|
if err != nil || !token.Valid {
|
||||||
// Valid: &valid,
|
log.Print("invalid refresh token")
|
||||||
// UserId: userID,
|
return auth.RefreshTokens401Response{}, nil
|
||||||
// Error: nil,
|
}
|
||||||
// Token: &newAccessToken, // return new access token
|
|
||||||
// // optionally return newRefreshToken as well
|
|
||||||
// }, nil
|
|
||||||
// } else {
|
|
||||||
// e := "user_id not found in refresh token"
|
|
||||||
// errStr = &e
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// e := "invalid refresh token claims"
|
|
||||||
// errStr = &e
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return auth.PostAuthVerifyToken200JSONResponse{
|
claims, ok := token.Claims.(*auth.TokenClaims)
|
||||||
// Valid: &valid,
|
if !ok || claims.Subject == "" {
|
||||||
// UserId: userID,
|
log.Print("invalid refresh token claims")
|
||||||
// Error: errStr,
|
return auth.RefreshTokens401Response{}, nil
|
||||||
// }, nil
|
}
|
||||||
// }
|
if claims.Type != "refresh" {
|
||||||
|
log.Errorf("token is not a refresh token")
|
||||||
|
return auth.RefreshTokens401Response{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, csrfToken, err := s.generateTokens(claims.Subject)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to generate tokens for user %s: %v", claims.Subject, err)
|
||||||
|
return auth.RefreshTokens500Response{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check cookie settings carefully
|
||||||
|
ginCtx.SetSameSite(http.SameSiteStrictMode)
|
||||||
|
ginCtx.SetCookie("access_token", accessToken, 900, "/api", "", false, true)
|
||||||
|
ginCtx.SetCookie("refresh_token", refreshToken, 1209600, "/auth", "", false, true)
|
||||||
|
ginCtx.SetCookie("xsrf_token", csrfToken, 1209600, "/", "", false, false)
|
||||||
|
|
||||||
|
return auth.RefreshTokens200Response{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) Logout(ctx context.Context, req auth.LogoutRequestObject) (auth.LogoutResponseObject, error) {
|
||||||
|
// TODO: get current tokens and add them to block list
|
||||||
|
ginCtx, ok := ctx.Value(gin.ContextKey).(*gin.Context)
|
||||||
|
if !ok {
|
||||||
|
log.Print("failed to get gin context")
|
||||||
|
return auth.Logout500Response{}, fmt.Errorf("failed to get gin.Context from context.Context")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete cookies by setting MaxAge negative
|
||||||
|
// TODO: change secure to true
|
||||||
|
ginCtx.SetCookie("access_token", "", -1, "/api", "", false, true)
|
||||||
|
ginCtx.SetCookie("refresh_token", "", -1, "/auth", "", false, true)
|
||||||
|
ginCtx.SetCookie("xsrf_token", "", -1, "/", "", false, false)
|
||||||
|
|
||||||
|
return auth.Logout200Response{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractBearerToken(header string) (string, error) {
|
func ExtractBearerToken(header string) (string, error) {
|
||||||
const prefix = "Bearer "
|
const prefix = "Bearer "
|
||||||
|
|
@ -277,3 +308,9 @@ func ExtractBearerToken(header string) (string, error) {
|
||||||
}
|
}
|
||||||
return header[len(prefix):], nil
|
return header[len(prefix):], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateJTI() string {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
_, _ = rand.Read(b)
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func main() {
|
||||||
|
|
||||||
log.Info("allow origins:", AppConfig.ServiceAddress)
|
log.Info("allow origins:", AppConfig.ServiceAddress)
|
||||||
r.Use(cors.New(cors.Config{
|
r.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: []string{"*"},
|
AllowOrigins: []string{AppConfig.ServiceAddress},
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,10 @@ func sqlDate2oapi(p_date pgtype.Timestamptz) *time.Time {
|
||||||
|
|
||||||
func oapiDate2sql(t *time.Time) pgtype.Timestamptz {
|
func oapiDate2sql(t *time.Time) pgtype.Timestamptz {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return pgtype.Timestamptz{Valid: false}
|
return pgtype.Timestamptz{
|
||||||
|
Time: time.Now(),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return pgtype.Timestamptz{
|
return pgtype.Timestamptz{
|
||||||
Time: *t,
|
Time: *t,
|
||||||
|
|
@ -167,7 +170,7 @@ func UserTitleStatus2Sqlc1(s *oapi.UserTitleStatus) (*sqlc.UsertitleStatusT, err
|
||||||
func (s Server) mapUsertitle(ctx context.Context, t sqlc.SearchUserTitlesRow) (oapi.UserTitle, error) {
|
func (s Server) mapUsertitle(ctx context.Context, t sqlc.SearchUserTitlesRow) (oapi.UserTitle, error) {
|
||||||
|
|
||||||
oapi_usertitle := oapi.UserTitle{
|
oapi_usertitle := oapi.UserTitle{
|
||||||
Ctime: &t.UserCtime,
|
Ftime: &t.UserFtime,
|
||||||
Rate: t.UserRate,
|
Rate: t.UserRate,
|
||||||
ReviewId: t.ReviewID,
|
ReviewId: t.ReviewID,
|
||||||
// Status: ,
|
// Status: ,
|
||||||
|
|
@ -386,6 +389,9 @@ func (s Server) AddUserTitle(ctx context.Context, request oapi.AddUserTitleReque
|
||||||
// fmt.Println(pgErr.Code) // => 42601
|
// fmt.Println(pgErr.Code) // => 42601
|
||||||
if pgErr.Code == pgErrDuplicateKey { //duplicate key value
|
if pgErr.Code == pgErrDuplicateKey { //duplicate key value
|
||||||
return oapi.AddUserTitle409Response{}, nil
|
return oapi.AddUserTitle409Response{}, nil
|
||||||
|
} else {
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
return oapi.AddUserTitle500Response{}, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
|
|
@ -398,7 +404,7 @@ func (s Server) AddUserTitle(ctx context.Context, request oapi.AddUserTitleReque
|
||||||
return oapi.AddUserTitle500Response{}, nil
|
return oapi.AddUserTitle500Response{}, nil
|
||||||
}
|
}
|
||||||
oapi_usertitle := oapi.UserTitleMini{
|
oapi_usertitle := oapi.UserTitleMini{
|
||||||
Ctime: &user_title.Ctime,
|
Ftime: &user_title.Ftime,
|
||||||
Rate: user_title.Rate,
|
Rate: user_title.Rate,
|
||||||
ReviewId: user_title.ReviewID,
|
ReviewId: user_title.ReviewID,
|
||||||
Status: oapi_status,
|
Status: oapi_status,
|
||||||
|
|
@ -457,7 +463,7 @@ func (s Server) UpdateUserTitle(ctx context.Context, request oapi.UpdateUserTitl
|
||||||
}
|
}
|
||||||
|
|
||||||
oapi_usertitle := oapi.UserTitleMini{
|
oapi_usertitle := oapi.UserTitleMini{
|
||||||
Ctime: &user_title.Ctime,
|
Ftime: &user_title.Ftime,
|
||||||
Rate: user_title.Rate,
|
Rate: user_title.Rate,
|
||||||
ReviewId: user_title.ReviewID,
|
ReviewId: user_title.ReviewID,
|
||||||
Status: oapi_status,
|
Status: oapi_status,
|
||||||
|
|
@ -487,7 +493,7 @@ func (s Server) GetUserTitle(ctx context.Context, request oapi.GetUserTitleReque
|
||||||
return oapi.GetUserTitle500Response{}, nil
|
return oapi.GetUserTitle500Response{}, nil
|
||||||
}
|
}
|
||||||
oapi_usertitle := oapi.UserTitleMini{
|
oapi_usertitle := oapi.UserTitleMini{
|
||||||
Ctime: &user_title.Ctime,
|
Ftime: &user_title.Ftime,
|
||||||
Rate: user_title.Rate,
|
Rate: user_title.Rate,
|
||||||
ReviewId: user_title.ReviewID,
|
ReviewId: user_title.ReviewID,
|
||||||
Status: oapi_status,
|
Status: oapi_status,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"nyanimedb/auth"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
@ -37,12 +40,18 @@ func JWTAuthMiddleware(secret string) gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Парсим токен с MapClaims
|
// 2. Парсим токен с MapClaims
|
||||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(tokenStr, &auth.TokenClaims{}, func(t *jwt.Token) (interface{}, error) {
|
||||||
if t.Method != jwt.SigningMethodHS256 {
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, errors.New("unexpected signing method: " + t.Method.Alg())
|
return nil, fmt.Errorf("unexpected signing method")
|
||||||
}
|
}
|
||||||
return []byte(secret), nil // ← конвертируем string → []byte
|
return []byte(secret), nil
|
||||||
})
|
})
|
||||||
|
// token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||||
|
// if t.Method != jwt.SigningMethodHS256 {
|
||||||
|
// return nil, errors.New("unexpected signing method: " + t.Method.Alg())
|
||||||
|
// }
|
||||||
|
// return []byte(secret), nil // ← конвертируем string → []byte
|
||||||
|
// })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
abortWithJSON(c, http.StatusUnauthorized, "invalid token: "+err.Error())
|
abortWithJSON(c, http.StatusUnauthorized, "invalid token: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -55,20 +64,23 @@ func JWTAuthMiddleware(secret string) gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Извлекаем user_id из claims
|
// 4. Извлекаем user_id из claims
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
claims, ok := token.Claims.(*auth.TokenClaims)
|
||||||
if !ok {
|
if !ok {
|
||||||
abortWithJSON(c, http.StatusUnauthorized, "invalid claims format")
|
abortWithJSON(c, http.StatusUnauthorized, "invalid claims format")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, ok := claims["user_id"].(string)
|
if claims.Subject == "" {
|
||||||
if !ok || userID == "" {
|
|
||||||
abortWithJSON(c, http.StatusUnauthorized, "user_id claim missing or invalid")
|
abortWithJSON(c, http.StatusUnauthorized, "user_id claim missing or invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if claims.Type != "access" {
|
||||||
|
abortWithJSON(c, http.StatusUnauthorized, "token type is not access")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Сохраняем в контексте
|
// 5. Сохраняем в контексте
|
||||||
c.Set("user_id", userID)
|
c.Set("user_id", claims.Subject)
|
||||||
|
|
||||||
// 6. Для oapi-codegen — кладём gin.Context в request context
|
// 6. Для oapi-codegen — кладём gin.Context в request context
|
||||||
GinContextToContext(c)
|
GinContextToContext(c)
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ SELECT
|
||||||
u.status as usertitle_status,
|
u.status as usertitle_status,
|
||||||
u.rate as user_rate,
|
u.rate as user_rate,
|
||||||
u.review_id as review_id,
|
u.review_id as review_id,
|
||||||
u.ctime as user_ctime,
|
u.ftime as user_ftime,
|
||||||
i.storage_type as title_storage_type,
|
i.storage_type as title_storage_type,
|
||||||
i.image_path as title_image_path,
|
i.image_path as title_image_path,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
|
|
@ -370,7 +370,7 @@ WHERE
|
||||||
AND (sqlc.narg('release_season')::release_season_t IS NULL OR t.release_season = sqlc.narg('release_season')::release_season_t)
|
AND (sqlc.narg('release_season')::release_season_t IS NULL OR t.release_season = sqlc.narg('release_season')::release_season_t)
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
t.id, u.user_id, u.status, u.rate, u.review_id, u.ctime, i.id, s.id
|
t.id, u.user_id, u.status, u.rate, u.review_id, u.ftime, i.id, s.id
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
CASE WHEN sqlc.arg('forward')::boolean THEN
|
CASE WHEN sqlc.arg('forward')::boolean THEN
|
||||||
|
|
@ -400,7 +400,7 @@ FROM reviews
|
||||||
WHERE review_id = sqlc.arg('review_id')::bigint;
|
WHERE review_id = sqlc.arg('review_id')::bigint;
|
||||||
|
|
||||||
-- name: InsertUserTitle :one
|
-- name: InsertUserTitle :one
|
||||||
INSERT INTO usertitles (user_id, title_id, status, rate, review_id, ctime)
|
INSERT INTO usertitles (user_id, title_id, status, rate, review_id, ftime)
|
||||||
VALUES (
|
VALUES (
|
||||||
sqlc.arg('user_id')::bigint,
|
sqlc.arg('user_id')::bigint,
|
||||||
sqlc.arg('title_id')::bigint,
|
sqlc.arg('title_id')::bigint,
|
||||||
|
|
@ -409,7 +409,7 @@ VALUES (
|
||||||
sqlc.narg('review_id')::bigint,
|
sqlc.narg('review_id')::bigint,
|
||||||
sqlc.narg('ftime')::timestamptz
|
sqlc.narg('ftime')::timestamptz
|
||||||
)
|
)
|
||||||
RETURNING user_id, title_id, status, rate, review_id, ctime;
|
RETURNING user_id, title_id, status, rate, review_id, ftime;
|
||||||
|
|
||||||
-- name: UpdateUserTitle :one
|
-- name: UpdateUserTitle :one
|
||||||
-- Fails with sql.ErrNoRows if (user_id, title_id) not found
|
-- Fails with sql.ErrNoRows if (user_id, title_id) not found
|
||||||
|
|
@ -417,7 +417,7 @@ UPDATE usertitles
|
||||||
SET
|
SET
|
||||||
status = COALESCE(sqlc.narg('status')::usertitle_status_t, status),
|
status = COALESCE(sqlc.narg('status')::usertitle_status_t, status),
|
||||||
rate = COALESCE(sqlc.narg('rate')::int, rate),
|
rate = COALESCE(sqlc.narg('rate')::int, rate),
|
||||||
ctime = COALESCE(sqlc.narg('ftime')::timestamptz, ctime)
|
ftime = COALESCE(sqlc.narg('ftime')::timestamptz, ftime)
|
||||||
WHERE
|
WHERE
|
||||||
user_id = sqlc.arg('user_id')
|
user_id = sqlc.arg('user_id')
|
||||||
AND title_id = sqlc.arg('title_id')
|
AND title_id = sqlc.arg('title_id')
|
||||||
|
|
|
||||||
2
modules/bot/.gitignore
vendored
2
modules/bot/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
generated-client/
|
|
||||||
out/
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.10.2)
|
|
||||||
project(AnimeBot)
|
|
||||||
|
|
||||||
set(SOURCES "")
|
|
||||||
file(GLOB_RECURSE SRC_FRONT "front/src/*.cpp")
|
|
||||||
list(APPEND SOURCES ${SRC_FRONT})
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SRC_BACK "back/src/*.cpp")
|
|
||||||
list(APPEND SOURCES ${SRC_BACK})
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SRC_API "generated-client/src/*.cpp")
|
|
||||||
list(APPEND SOURCES ${SRC_API})
|
|
||||||
|
|
||||||
file(GLOB_RECURSE SRC_AUTH "generated-client-auth/src/*.cpp")
|
|
||||||
list(APPEND SOURCES ${SRC_AUTH})
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
|
||||||
set(Boost_USE_MULTITHREADED ON)
|
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
find_package(Boost COMPONENTS system REQUIRED)
|
|
||||||
find_package(CURL)
|
|
||||||
find_library(CPPREST_LIB cpprest REQUIRED)
|
|
||||||
include_directories(/usr/local/include ${OPENSSL_INCLUDE_DIR} ${Boost_INCLUDE_DIR})
|
|
||||||
include_directories(front/include/)
|
|
||||||
include_directories(back/include)
|
|
||||||
include_directories(generated-client/include)
|
|
||||||
include_directories(generated-client-auth/include)
|
|
||||||
if (CURL_FOUND)
|
|
||||||
include_directories(${CURL_INCLUDE_DIRS})
|
|
||||||
add_definitions(-DHAVE_CURL)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(AnimeBot ${SOURCES})
|
|
||||||
|
|
||||||
target_link_libraries(AnimeBot /usr/local/lib/libTgBot.a ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} ${CURL_LIBRARIES} ${CPPREST_LIB})
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
### Здесь будет часть, отвечающая за телеграм ботика
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// AuthImpersonationClient.hpp
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <map>
|
|
||||||
#include <cpprest/asyncrt_utils.h>
|
|
||||||
#include "AuthClient/ApiClient.h"
|
|
||||||
#include "AuthClient/ApiConfiguration.h"
|
|
||||||
#include "AuthClient/api/AuthApi.h"
|
|
||||||
#include "AuthClient/model/GetImpersonationToken_request.h"
|
|
||||||
#include "AuthClient/model/GetImpersonationToken_200_response.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace nyanimed {
|
|
||||||
|
|
||||||
class AuthImpersonationClient {
|
|
||||||
public:
|
|
||||||
AuthImpersonationClient();
|
|
||||||
|
|
||||||
// Потокобезопасный вызов — не модифицирует состояние
|
|
||||||
pplx::task<std::shared_ptr<nyanimed::meow::auth::model::GetImpersonationToken_200_response>>
|
|
||||||
getImpersonationToken(int64_t userId) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_baseUrl;
|
|
||||||
std::string m_authToken;
|
|
||||||
std::shared_ptr<nyanimed::meow::auth::api::ApiClient> m_apiClient;
|
|
||||||
std::shared_ptr<nyanimed::meow::auth::api::AuthApi> m_authApi;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace nyanimed
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "CppRestOpenAPIClient/ApiClient.h"
|
|
||||||
#include "CppRestOpenAPIClient/ApiConfiguration.h"
|
|
||||||
#include "CppRestOpenAPIClient/api/DefaultApi.h"
|
|
||||||
#include "CppRestOpenAPIClient/model/User.h"
|
|
||||||
#include "CppRestOpenAPIClient/model/GetUserTitles_200_response.h"
|
|
||||||
#include "CppRestOpenAPIClient/model/UserTitle.h"
|
|
||||||
#include "CppRestOpenAPIClient/model/Title.h"
|
|
||||||
#include "constants.hpp"
|
|
||||||
#include "structs.hpp"
|
|
||||||
#include <iostream>
|
|
||||||
#include <thread>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <cpprest/asyncrt_utils.h>
|
|
||||||
#include <boost/optional.hpp>
|
|
||||||
|
|
||||||
#include "AuthImpersonation.hpp"
|
|
||||||
|
|
||||||
using namespace org::openapitools::client::api;
|
|
||||||
|
|
||||||
class BotToServer {
|
|
||||||
public:
|
|
||||||
BotToServer();
|
|
||||||
|
|
||||||
// Асинхронный метод: получить список тайтлов пользователя
|
|
||||||
pplx::task<std::vector<BotStructs::Title>> fetchUserTitlesAsync(const std::string& userId);
|
|
||||||
pplx::task<BotStructs::Title> fetchTitleAsync(const std::string& userId, int64_t titleId);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<org::openapitools::client::api::ApiConfiguration> apiconfiguration;
|
|
||||||
std::shared_ptr<org::openapitools::client::api::ApiClient> apiclient;
|
|
||||||
std::shared_ptr<org::openapitools::client::api::DefaultApi> api;
|
|
||||||
|
|
||||||
nyanimed::AuthImpersonationClient authClient;
|
|
||||||
|
|
||||||
static BotStructs::Title mapTitleToBotTitle(
|
|
||||||
const std::shared_ptr<org::openapitools::client::model::Title>& titleModel);
|
|
||||||
};
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
#include "AuthImpersonation.hpp"
|
|
||||||
|
|
||||||
nyanimed::AuthImpersonationClient::AuthImpersonationClient() {
|
|
||||||
const char* baseUrlEnv = std::getenv("NYANIMEDBAUTHURL");
|
|
||||||
const char* tokenEnv = std::getenv("NYANIMEDBAUTHTOKEN");
|
|
||||||
|
|
||||||
if (!baseUrlEnv || std::string(baseUrlEnv).empty()) {
|
|
||||||
throw std::runtime_error("Missing required environment variable: NYANIMEDBAUTHURL");
|
|
||||||
}
|
|
||||||
if (!tokenEnv || std::string(tokenEnv).empty()) {
|
|
||||||
throw std::runtime_error("Missing required environment variable: NYANIMEDBAUTHTOKEN");
|
|
||||||
}
|
|
||||||
|
|
||||||
m_baseUrl = baseUrlEnv;
|
|
||||||
m_authToken = tokenEnv;
|
|
||||||
|
|
||||||
auto config = std::make_shared<nyanimed::meow::auth::api::ApiConfiguration>();
|
|
||||||
config->setBaseUrl(utility::conversions::to_string_t(m_baseUrl));
|
|
||||||
|
|
||||||
m_apiClient = std::make_shared<nyanimed::meow::auth::api::ApiClient>(config);
|
|
||||||
m_authApi = std::make_shared<nyanimed::meow::auth::api::AuthApi>(m_apiClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
pplx::task<std::shared_ptr<nyanimed::meow::auth::model::GetImpersonationToken_200_response>>
|
|
||||||
nyanimed::AuthImpersonationClient::getImpersonationToken(int64_t userId) const {
|
|
||||||
auto request = std::make_shared<nyanimed::meow::auth::model::GetImpersonationToken_request>();
|
|
||||||
request->setUserId(userId);
|
|
||||||
//request->setExternalId(externalId);
|
|
||||||
|
|
||||||
std::map<utility::string_t, utility::string_t> headers;
|
|
||||||
headers[U("Authorization")] = U("Bearer ") + utility::conversions::to_string_t(m_authToken);
|
|
||||||
|
|
||||||
return m_authApi->getImpersonationToken(request, headers);
|
|
||||||
}
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
#include "BotToServer.hpp"
|
|
||||||
|
|
||||||
BotToServer::BotToServer() {
|
|
||||||
apiconfiguration = std::make_shared<ApiConfiguration>();
|
|
||||||
const char* envUrl = getenv("NYANIMEDBBASEURL");
|
|
||||||
if (!envUrl) {
|
|
||||||
std::runtime_error("Environment variable NYANIMEDBBASEURL is not set");
|
|
||||||
}
|
|
||||||
apiconfiguration->setBaseUrl(utility::conversions::to_string_t(envUrl));
|
|
||||||
apiconfiguration->setUserAgent(utility::conversions::to_string_t("OpenAPI Client"));
|
|
||||||
apiclient = std::make_shared<ApiClient>(apiconfiguration);
|
|
||||||
api = std::make_shared<DefaultApi>(apiclient);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вспомогательная функция: преобразует UserTitle → BotStructs::Title
|
|
||||||
static BotStructs::Title mapUserTitleToBotTitle(
|
|
||||||
const std::shared_ptr<org::openapitools::client::model::UserTitle>& userTitle
|
|
||||||
) {
|
|
||||||
if (!userTitle || !userTitle->titleIsSet()) {
|
|
||||||
return BotStructs::Title{0, "Invalid", "", -1};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto apiTitle = userTitle->getTitle();
|
|
||||||
if (!apiTitle) {
|
|
||||||
return BotStructs::Title{0, "No Title", "", -1};
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t id = apiTitle->getId();
|
|
||||||
|
|
||||||
std::string name = "Untitled";
|
|
||||||
auto titleNames = apiTitle->getTitleNames();
|
|
||||||
utility::string_t ru = U("ru");
|
|
||||||
|
|
||||||
if (titleNames.find(ru) != titleNames.end() && !titleNames.at(ru).empty()) {
|
|
||||||
name = utility::conversions::to_utf8string(titleNames.at(ru).front());
|
|
||||||
} else if (!titleNames.empty()) {
|
|
||||||
const auto& firstLang = *titleNames.begin();
|
|
||||||
if (!firstLang.second.empty()) {
|
|
||||||
name = utility::conversions::to_utf8string(firstLang.second.front());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string description = ""; // описание пока не поддерживается в OpenAPI-модели
|
|
||||||
return BotStructs::Title{id, name, description, -1};
|
|
||||||
}
|
|
||||||
|
|
||||||
pplx::task<std::vector<BotStructs::Title>> BotToServer::fetchUserTitlesAsync(const std::string& userId) {
|
|
||||||
// Шаг 1: Получаем impersonation-токен
|
|
||||||
auto impersonationTask = authClient.getImpersonationToken(std::stoi(userId));
|
|
||||||
|
|
||||||
// Шаг 2: После получения токена — делаем запрос getUserTitles с этим токеном
|
|
||||||
return impersonationTask.then([=](pplx::task<std::shared_ptr<nyanimed::meow::auth::model::GetImpersonationToken_200_response>> tokenTask) {
|
|
||||||
try {
|
|
||||||
auto tokenResponse = tokenTask.get();
|
|
||||||
if (!tokenResponse) {
|
|
||||||
throw std::runtime_error("Null response from getImpersonationToken");
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t accessToken = utility::conversions::to_string_t(tokenResponse->getAccessToken());
|
|
||||||
|
|
||||||
// Формируем заголовки с токеном
|
|
||||||
std::map<utility::string_t, utility::string_t> customHeaders;
|
|
||||||
customHeaders[U("Cookie")] = U("access_token=") + accessToken;
|
|
||||||
|
|
||||||
// Подготавливаем параметры запроса
|
|
||||||
utility::string_t userIdW = utility::conversions::to_string_t(userId);
|
|
||||||
int32_t limit = static_cast<int32_t>(BotConstants::DISP_TITLES_NUM);
|
|
||||||
|
|
||||||
// Шаг 3: Выполняем getUserTitles с кастомными заголовками
|
|
||||||
return api->getUserTitles(
|
|
||||||
userIdW,
|
|
||||||
boost::none, // cursor
|
|
||||||
boost::none, // sort
|
|
||||||
boost::none, // sortForward
|
|
||||||
boost::none, // word
|
|
||||||
boost::none, // status
|
|
||||||
boost::none, // watchStatus
|
|
||||||
boost::none, // rating
|
|
||||||
boost::none, // myRate
|
|
||||||
boost::none, // releaseYear
|
|
||||||
boost::none, // releaseSeason
|
|
||||||
limit,
|
|
||||||
boost::none, // fields
|
|
||||||
customHeaders
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error obtaining impersonation token: " << e.what() << std::endl;
|
|
||||||
throw; // Пробрасываем, чтобы цепочка task.then завершилась с ошибкой
|
|
||||||
}
|
|
||||||
}).then([=](pplx::task<std::shared_ptr<org::openapitools::client::model::GetUserTitles_200_response>> responseTask) {
|
|
||||||
try {
|
|
||||||
auto response = responseTask.get();
|
|
||||||
if (!response) {
|
|
||||||
throw std::runtime_error("Null response from getUserTitles");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& userTitles = response->getData();
|
|
||||||
std::vector<BotStructs::Title> result;
|
|
||||||
result.reserve(userTitles.size());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < userTitles.size(); ++i) {
|
|
||||||
BotStructs::Title botTitle = mapUserTitleToBotTitle(userTitles[i]);
|
|
||||||
botTitle.num = static_cast<int64_t>(i); // 0-based индекс
|
|
||||||
result.push_back(botTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (const web::http::http_exception& e) {
|
|
||||||
std::cerr << "HTTP error in fetchUserTitlesAsync: " << e.what() << std::endl;
|
|
||||||
throw;
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error in fetchUserTitlesAsync: " << e.what() << std::endl;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pplx::task<BotStructs::Title> BotToServer::fetchTitleAsync(const std::string& userId, int64_t titleId) {
|
|
||||||
auto impersonationTask = authClient.getImpersonationToken(std::stoi(userId));
|
|
||||||
|
|
||||||
// Шаг 2: После получения токена — делаем запрос getTitle с этим токеном
|
|
||||||
return impersonationTask.then([=](pplx::task<std::shared_ptr<nyanimed::meow::auth::model::GetImpersonationToken_200_response>> tokenTask) {
|
|
||||||
try {
|
|
||||||
auto tokenResponse = tokenTask.get();
|
|
||||||
if (!tokenResponse) {
|
|
||||||
throw std::runtime_error("Null response from getImpersonationToken");
|
|
||||||
}
|
|
||||||
|
|
||||||
utility::string_t accessToken = utility::conversions::to_string_t(tokenResponse->getAccessToken());
|
|
||||||
|
|
||||||
// Формируем заголовки с токеном
|
|
||||||
std::map<utility::string_t, utility::string_t> customHeaders;
|
|
||||||
customHeaders[U("Cookie")] = U("access_token=") + accessToken;
|
|
||||||
|
|
||||||
// Шаг 3: Выполняем запрос getTitle
|
|
||||||
return api->getTitle(
|
|
||||||
titleId,
|
|
||||||
boost::none, // fields — оставляем по умолчанию (все поля)
|
|
||||||
customHeaders
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error obtaining impersonation token in fetchTitleAsync: " << e.what() << std::endl;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}).then([](pplx::task<std::shared_ptr<org::openapitools::client::model::Title>> responseTask) {
|
|
||||||
try {
|
|
||||||
auto response = responseTask.get();
|
|
||||||
if (!response) {
|
|
||||||
throw std::runtime_error("Null response from getTitle");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Преобразуем модель OpenAPI в внутреннюю структуру бота
|
|
||||||
BotStructs::Title botTitle = mapTitleToBotTitle(response);
|
|
||||||
return botTitle;
|
|
||||||
|
|
||||||
} catch (const web::http::http_exception& e) {
|
|
||||||
std::cerr << "HTTP error in fetchTitleAsync: " << e.what() << std::endl;
|
|
||||||
throw;
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error in fetchTitleAsync: " << e.what() << std::endl;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
BotStructs::Title BotToServer::mapTitleToBotTitle(
|
|
||||||
const std::shared_ptr<org::openapitools::client::model::Title>& titleModel)
|
|
||||||
{
|
|
||||||
if (!titleModel) {
|
|
||||||
throw std::invalid_argument("titleModel is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
BotStructs::Title botTitle;
|
|
||||||
botTitle.id = titleModel->getId();
|
|
||||||
botTitle.num = 0;
|
|
||||||
botTitle.description = ""; // Описание недоступно в текущей модели
|
|
||||||
|
|
||||||
// Извлекаем название
|
|
||||||
std::string titleName;
|
|
||||||
const auto& titleNames = titleModel->getTitleNames();
|
|
||||||
|
|
||||||
// Попробуем ru → en → первый попавшийся
|
|
||||||
std::vector<utility::string_t> preferredLangs = { U("ru"), U("en") };
|
|
||||||
bool found = false;
|
|
||||||
for (const auto& lang : preferredLangs) {
|
|
||||||
auto it = titleNames.find(lang);
|
|
||||||
if (it != titleNames.end() && !it->second.empty()) {
|
|
||||||
titleName = utility::conversions::to_utf8string(it->second[0]);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found && !titleNames.empty()) {
|
|
||||||
// Берём первый язык и первое название
|
|
||||||
const auto& firstLang = *titleNames.begin();
|
|
||||||
if (!firstLang.second.empty()) {
|
|
||||||
titleName = utility::conversions::to_utf8string(firstLang.second[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
botTitle.name = titleName;
|
|
||||||
|
|
||||||
// --- Изображение ---
|
|
||||||
botTitle.imageUrl = "";
|
|
||||||
if (titleModel->posterIsSet()) {
|
|
||||||
auto poster = titleModel->getPoster();
|
|
||||||
if (poster && poster->imagePathIsSet()) {
|
|
||||||
botTitle.imageUrl = utility::conversions::to_utf8string(poster->getImagePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return botTitle;
|
|
||||||
}
|
|
||||||
2
modules/bot/front/.gitignore
vendored
2
modules/bot/front/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
build/
|
|
||||||
out/
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include <mutex>
|
|
||||||
#include <optional>
|
|
||||||
#include "constants.hpp"
|
|
||||||
|
|
||||||
enum class UserState {
|
|
||||||
MAIN_MENU, // Главное меню
|
|
||||||
VIEWING_MY_TITLES, // Список моих тайтлов
|
|
||||||
AWAITING_TITLE_NAME, // Жду название тайтла для поиска
|
|
||||||
VIEWING_FOUND_TITLES, // Смотрю найденные тайтлы
|
|
||||||
VIEWING_TITLE_PAGE, // Смотрю страничку тайтла
|
|
||||||
AWAITING_REVIEW, // Жду ревью на тайтл
|
|
||||||
VIEWING_REVIEW_LIST, // Смотрю список ревью на тайтл
|
|
||||||
VIEWING_REVIEW, // Смотрю (конкретное) ревью на тайтл
|
|
||||||
VIEWING_DESCRIPTION, // Смотрю описание тайтла
|
|
||||||
ERROR, // Ошибка состояния
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NavigationStep {
|
|
||||||
UserState state;
|
|
||||||
int64_t payload; // ID тайтла, ревью и т.д.
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct UserContext {
|
|
||||||
int64_t userId;
|
|
||||||
std::vector<NavigationStep> history; // Текущее состояние пользователя + история предыдущих состояний
|
|
||||||
};
|
|
||||||
|
|
||||||
class BotUserContext {
|
|
||||||
private:
|
|
||||||
mutable std::mutex mtx;
|
|
||||||
std::unordered_map<int64_t, UserContext> userContexts;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Получить копию контекста пользователя (или std::nullopt, если не найден)
|
|
||||||
std::optional<UserContext> getContext(int64_t userId) const;
|
|
||||||
|
|
||||||
// Установить/обновить контекст пользователя
|
|
||||||
void setContext(int64_t userId, const UserContext& context);
|
|
||||||
|
|
||||||
// Добавить шаг навигации к существующему контексту пользователя
|
|
||||||
// Если пользователя нет — создаётся новый контекст
|
|
||||||
void pushNavigationStep(int64_t userId, const NavigationStep& step);
|
|
||||||
|
|
||||||
// Заменить текущую историю (полезно, например, при сбросе состояния)
|
|
||||||
void setNavigationHistory(int64_t userId, const std::vector<NavigationStep>& history);
|
|
||||||
|
|
||||||
// Получить текущий шаг (последний в истории) или std::nullopt, если нет истории
|
|
||||||
std::optional<NavigationStep> getCurrentStep(int64_t userId) const;
|
|
||||||
|
|
||||||
// pop последнего состояния. true в случае удачи
|
|
||||||
bool popStep(int64_t userId);
|
|
||||||
|
|
||||||
// Удалить контекст пользователя (например, при логауте)
|
|
||||||
void removeContext(int64_t userId);
|
|
||||||
|
|
||||||
/// @brief Создает контекст начального меню для пользователя
|
|
||||||
/// @param userId
|
|
||||||
void createInitContext(int64_t userId);
|
|
||||||
};
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
#include <tgbot/tgbot.h>
|
|
||||||
#include "structs.hpp"
|
|
||||||
|
|
||||||
class KeyboardFactory {
|
|
||||||
public:
|
|
||||||
/// Create keyboard for main menu
|
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createMainMenu();
|
|
||||||
|
|
||||||
/// Create keyboard for My_Titles
|
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createMyTitles(std::vector<BotStructs::Title> titles);
|
|
||||||
|
|
||||||
/// Create keyboard for sendError
|
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createError(const std::string& errorCallback);
|
|
||||||
|
|
||||||
/// Create keyboard for Title page
|
|
||||||
static TgBot::InlineKeyboardMarkup::Ptr createTitleMenu(int64_t title_id);
|
|
||||||
};
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace BotConstants {
|
|
||||||
const int64_t NULL_PAYLOAD = -1; // Default value для payload
|
|
||||||
const int64_t DISP_TITLES_NUM = 6; // Количество тайтлов, отображаемых на страничке
|
|
||||||
const int64_t DISP_REVIEW_NUM = 4; // Количество ревью, отображаемых на страничке
|
|
||||||
|
|
||||||
namespace Button {
|
|
||||||
const std::string TO_MAIN_MENU = "Главное меню";
|
|
||||||
const std::string FIND_ANIME = "Найти аниме";
|
|
||||||
const std::string MY_TITLES = "Мои тайтлы";
|
|
||||||
const std::string PREV = "<<Назад";
|
|
||||||
const std::string NEXT = "Дальше>>";
|
|
||||||
}
|
|
||||||
namespace Callback {
|
|
||||||
const std::string ACTION = "action:";
|
|
||||||
const std::string FIND_ANIME = ACTION + "find_anime";
|
|
||||||
const std::string ADD_REVIEW = ACTION + "add_review";
|
|
||||||
const std::string ADD_STATUS = ACTION + "add_status";
|
|
||||||
const std::string STATUS = "status:";
|
|
||||||
const std::string WATCHING = STATUS + "watching";
|
|
||||||
const std::string SEEN = STATUS + "seen";
|
|
||||||
const std::string WANT = STATUS + "want";
|
|
||||||
const std::string THROWN = STATUS + "thrown";
|
|
||||||
const std::string NAVIGATION = "navigation:";
|
|
||||||
const std::string MAIN_MENU = NAVIGATION + "main_menu";
|
|
||||||
const std::string MY_TITLES = NAVIGATION + "my_titles";
|
|
||||||
const std::string LIST_PREV = NAVIGATION + "prev"; // Пагинация
|
|
||||||
const std::string LIST_NEXT = NAVIGATION + "next"; // Пагинация
|
|
||||||
const std::string NAV_BACK = NAVIGATION + "back"; // Возврат по стеку состояний
|
|
||||||
const std::string CHOICE = "choice:";
|
|
||||||
const std::string ERROR = "error:";
|
|
||||||
const std::string ERROR_NAVIGATION = ERROR + NAVIGATION;
|
|
||||||
const std::string ERROR_AUTH = ERROR + "auth";
|
|
||||||
}
|
|
||||||
namespace Text {
|
|
||||||
const std::string MAIN_MENU = "Вас приветствует nyanimedb бот:)\nЧего будем делать?";
|
|
||||||
const std::string SAD_ERROR = "У нас что-то случилось:(\nМы обязательно скоро исправимся";
|
|
||||||
const std::string AUTH_ERROR = "Проблемы с авторизацией, попробуйте авторизоваться повторно";
|
|
||||||
const std::string SERVER_ERROR = "Не удалось загрузить данные. Попробуйте позже.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#include <csignal>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <exception>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <tgbot/tgbot.h>
|
|
||||||
#include "handlers.hpp"
|
|
||||||
|
|
||||||
class AnimeBot {
|
|
||||||
private:
|
|
||||||
TgBot::Bot bot;
|
|
||||||
BotHandlers handler;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Init Bot
|
|
||||||
AnimeBot(const std::string& token);
|
|
||||||
|
|
||||||
/// Main menu
|
|
||||||
void setupHandlers();
|
|
||||||
|
|
||||||
/// Get TgBot::Bot
|
|
||||||
TgBot::Bot& getBot();
|
|
||||||
|
|
||||||
/// Function creates main menu and sends it to user with chatId
|
|
||||||
void sendMainMenu(int64_t chatId);
|
|
||||||
};
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <tgbot/tgbot.h>
|
|
||||||
#include <string>
|
|
||||||
#include <structs.hpp>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <cctype>
|
|
||||||
#include "BotToServer.hpp"
|
|
||||||
#include "BotUserContext.hpp"
|
|
||||||
|
|
||||||
/// @brief Структура возвращаемого значения класса BotHandlers для изменения текущего сообщения
|
|
||||||
struct HandlerResult {
|
|
||||||
std::string message;
|
|
||||||
TgBot::InlineKeyboardMarkup::Ptr keyboard;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BotHandlers {
|
|
||||||
public:
|
|
||||||
BotHandlers(TgBot::Api api) : botApi(api) {;}
|
|
||||||
|
|
||||||
/// @brief Обработка callback'ов из кнопок интерфейса
|
|
||||||
/// @param query запрос callback
|
|
||||||
void handleCallback(TgBot::CallbackQuery::Ptr query);
|
|
||||||
|
|
||||||
/// @brief Обработка сообщений боту
|
|
||||||
/// @details
|
|
||||||
/// Функция для обработки сообщений, которые юзер отправляет
|
|
||||||
/// боту. Необходима для обработки ревью и названий искомого
|
|
||||||
/// аниме. Внутри себя проверяет текущий state пользователя
|
|
||||||
/// в боте.
|
|
||||||
/// @param message обрабатываемое сообщение
|
|
||||||
void handleMessage(TgBot::Message::Ptr message);
|
|
||||||
|
|
||||||
void initUser(int64_t userId);
|
|
||||||
private:
|
|
||||||
TgBot::Api botApi;
|
|
||||||
BotUserContext contextManager;
|
|
||||||
BotToServer server_;
|
|
||||||
|
|
||||||
void handleNavigation(TgBot::CallbackQuery::Ptr query);
|
|
||||||
|
|
||||||
void handleError(TgBot::CallbackQuery::Ptr query);
|
|
||||||
|
|
||||||
void processCallbackImpl(TgBot::CallbackQuery::Ptr query);
|
|
||||||
|
|
||||||
/// @brief Получить очередную страницу тайтлов из списка пользователя
|
|
||||||
/// @param userId Идентификатор пользователя
|
|
||||||
/// @param payload Полезная нагрузка
|
|
||||||
/// @return HandlerResult
|
|
||||||
/// static HandlerResult returnMyTitles(int64_t userId, int64_t payload);
|
|
||||||
|
|
||||||
/// @brief Уменьшает значение нагрузки с учетом текущего состояния
|
|
||||||
/// @param payload Изменяемое значение нагрузки
|
|
||||||
/// @param curState Текущее состояние
|
|
||||||
void reducePayload(int64_t& payload, const UserState curState);
|
|
||||||
|
|
||||||
/// @brief Увеличивает значение нагрузки с учетом текущего состояния
|
|
||||||
/// @param payload Изменяемое значение нагрузки
|
|
||||||
/// @param curState Текущее состояние
|
|
||||||
void increasePayload(int64_t& payload, const UserState curState);
|
|
||||||
|
|
||||||
/// @brief Редактирует текущее сообщение в диалоге с пользователем
|
|
||||||
/// @details Меняет текст сообщения и клавиатуру на те, что передаются
|
|
||||||
/// в аргументе response. Информацию об id чата и изменяемого сообщения
|
|
||||||
/// забирает из query, который возвращается с callback'ом после нажатия
|
|
||||||
/// кнопки в интерфейсе
|
|
||||||
/// @param query Callback запрос
|
|
||||||
/// @param response Параметры ответа: клавиатура и текст
|
|
||||||
void editMessage(int64_t chatId, int64_t messageId, HandlerResult response);
|
|
||||||
|
|
||||||
/// @brief Редактирует текущее сообщение в диалоге с пользователем
|
|
||||||
/// @details Меняет текст сообщения и клавиатуру на те, что передаются
|
|
||||||
/// в аргументе response, а также прикрепляет картинку
|
|
||||||
/// @param chatId id чата
|
|
||||||
/// @param messageId id меняемого сообщения
|
|
||||||
/// @param imageUrl ссылка на вставляемую картинку (должна быть доступна в публичной сети)
|
|
||||||
/// @param response Параметры ответа: клавиатура и текст
|
|
||||||
void editMessageWithPhoto(
|
|
||||||
int64_t chatId,
|
|
||||||
int64_t messageId,
|
|
||||||
const std::string& imageUrl,
|
|
||||||
HandlerResult response);
|
|
||||||
|
|
||||||
/// @brief Отрисовка текущего экрана (соотв. контексту)
|
|
||||||
/// @param ctx - текущий контекст
|
|
||||||
void renderCurrent(TgBot::CallbackQuery::Ptr query);
|
|
||||||
|
|
||||||
/// @brief Логика переходов между контекстами (навигация на следующий шаг)
|
|
||||||
/// @param query - запрос
|
|
||||||
/// @param current - текущий шаг навигации
|
|
||||||
/// @return следующий NavigationStep при успехе (std::nullopt в случае ошибки)
|
|
||||||
std::optional<NavigationStep> computeNextStep(const TgBot::CallbackQuery::Ptr& query,
|
|
||||||
const NavigationStep& current);
|
|
||||||
|
|
||||||
/// @brief Получить состояние страницы главного меню
|
|
||||||
/// @return HandlerResult с параметрами главного меню
|
|
||||||
HandlerResult showMainMenu();
|
|
||||||
|
|
||||||
/// @brief Посылает интерфейс обработки ошибки на callback запрос
|
|
||||||
/// @param query запрос
|
|
||||||
void sendError(int64_t chatId, int64_t messageId, const std::string& errText);
|
|
||||||
|
|
||||||
// Форматирование для отображения в сообщении
|
|
||||||
std::string formatTitlesList(const std::vector<BotStructs::Title>& titles);
|
|
||||||
|
|
||||||
// Форматирование сообщения на страничке с тайтлом
|
|
||||||
std::string formatTitle(const BotStructs::Title& title);
|
|
||||||
|
|
||||||
// Парсинг id тайтла из callback'а
|
|
||||||
int64_t parseId(const std::string& data);
|
|
||||||
};
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace BotStructs {
|
|
||||||
struct Title {
|
|
||||||
int64_t id;
|
|
||||||
std::string name;
|
|
||||||
std::string description;
|
|
||||||
int64_t num;
|
|
||||||
std::string imageUrl;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#include "BotUserContext.hpp"
|
|
||||||
|
|
||||||
std::optional<UserContext> BotUserContext::getContext(int64_t userId) const {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
auto it = userContexts.find(userId);
|
|
||||||
if (it != userContexts.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotUserContext::setContext(int64_t userId, const UserContext& context) {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
userContexts[userId] = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotUserContext::pushNavigationStep(int64_t userId, const NavigationStep& step) {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
auto& ctx = userContexts[userId];
|
|
||||||
ctx.userId = userId;
|
|
||||||
ctx.history.push_back(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotUserContext::setNavigationHistory(int64_t userId, const std::vector<NavigationStep>& history) {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
auto& ctx = userContexts[userId];
|
|
||||||
ctx.userId = userId;
|
|
||||||
ctx.history = history;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<NavigationStep> BotUserContext::getCurrentStep(int64_t userId) const {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
auto it = userContexts.find(userId);
|
|
||||||
if (it != userContexts.end() && !it->second.history.empty()) {
|
|
||||||
return it->second.history.back();
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotUserContext::removeContext(int64_t userId) {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
userContexts.erase(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BotUserContext::popStep(int64_t userId) {
|
|
||||||
std::lock_guard<std::mutex> lock(mtx);
|
|
||||||
auto it = userContexts.find(userId);
|
|
||||||
if (it != userContexts.end() && (it->second.history.size() > 1)) {
|
|
||||||
it->second.history.pop_back();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotUserContext::createInitContext(int64_t userId) {
|
|
||||||
NavigationStep initStep = {UserState::MAIN_MENU, BotConstants::NULL_PAYLOAD};
|
|
||||||
setContext(userId, {userId, {initStep}});
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
#include "KeyboardFactory.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
|
|
||||||
TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMainMenu() {
|
|
||||||
auto keyboard = std::make_shared<TgBot::InlineKeyboardMarkup>();
|
|
||||||
TgBot::InlineKeyboardButton::Ptr button1(new TgBot::InlineKeyboardButton);
|
|
||||||
button1->text = BotConstants::Button::FIND_ANIME;
|
|
||||||
button1->callbackData = BotConstants::Callback::FIND_ANIME;
|
|
||||||
TgBot::InlineKeyboardButton::Ptr button2(new TgBot::InlineKeyboardButton);
|
|
||||||
button2->text = BotConstants::Button::MY_TITLES;
|
|
||||||
button2->callbackData = BotConstants::Callback::MY_TITLES;
|
|
||||||
|
|
||||||
keyboard->inlineKeyboard = {{button1, button2}};
|
|
||||||
return keyboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Переписать с учетом констант на количество отображаемых тайтлов и нового callback'a
|
|
||||||
TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createMyTitles(std::vector<BotStructs::Title> titles) {
|
|
||||||
auto keyboard = std::make_shared<TgBot::InlineKeyboardMarkup>();
|
|
||||||
std::vector<TgBot::InlineKeyboardButton::Ptr> row;
|
|
||||||
std::vector<std::vector<TgBot::InlineKeyboardButton::Ptr>> layout;
|
|
||||||
|
|
||||||
int counter = 0;
|
|
||||||
for(BotStructs::Title& title : titles) {
|
|
||||||
if(counter >= BotConstants::DISP_TITLES_NUM) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto button = std::make_shared<TgBot::InlineKeyboardButton>();
|
|
||||||
button->text = std::to_string(title.num + 1) + " " + title.name;
|
|
||||||
button->callbackData = BotConstants::Callback::CHOICE + std::to_string(title.num);
|
|
||||||
row.push_back(button);
|
|
||||||
counter++;
|
|
||||||
if(counter % 2 == 0) {
|
|
||||||
layout.push_back(row);
|
|
||||||
row.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!row.empty()) {
|
|
||||||
layout.push_back(row);
|
|
||||||
row.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Додумать логику, когда пришло 6 записей в конце
|
|
||||||
if(counter % 2 == 1) {
|
|
||||||
auto button = std::make_shared<TgBot::InlineKeyboardButton>();
|
|
||||||
button->text = BotConstants::Button::PREV;
|
|
||||||
if(titles[0].num == 0) {
|
|
||||||
button->callbackData = BotConstants::Callback::NAV_BACK;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
button->callbackData = BotConstants::Callback::LIST_PREV + ':' + std::to_string(titles[0].num);
|
|
||||||
}
|
|
||||||
layout.back().push_back(button);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
auto button_prev = std::make_shared<TgBot::InlineKeyboardButton>();
|
|
||||||
button_prev->text = BotConstants::Button::PREV;
|
|
||||||
if(titles[0].num == 0) {
|
|
||||||
button_prev->callbackData = BotConstants::Callback::NAV_BACK;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
button_prev->callbackData = BotConstants::Callback::LIST_PREV + ':' + std::to_string(titles[0].num);
|
|
||||||
}
|
|
||||||
auto button_next = std::make_shared<TgBot::InlineKeyboardButton>();
|
|
||||||
button_next->text = BotConstants::Button::NEXT;
|
|
||||||
button_next->callbackData = BotConstants::Callback::LIST_NEXT + ':' + std::to_string(titles[5].num);
|
|
||||||
layout.push_back({button_prev, button_next});
|
|
||||||
}
|
|
||||||
|
|
||||||
keyboard->inlineKeyboard = layout;
|
|
||||||
|
|
||||||
return keyboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
TgBot::InlineKeyboardMarkup::Ptr KeyboardFactory::createError(const std::string& errorCallback) {
|
|
||||||
auto keyboard = std::make_shared<TgBot::InlineKeyboardMarkup>();
|
|
||||||
TgBot::InlineKeyboardButton::Ptr button(new TgBot::InlineKeyboardButton);
|
|
||||||
button->text = BotConstants::Button::TO_MAIN_MENU;
|
|
||||||
button->callbackData = errorCallback;
|
|
||||||
|
|
||||||
keyboard->inlineKeyboard = {{button}};
|
|
||||||
return keyboard;
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#include "front.hpp"
|
|
||||||
#include "KeyboardFactory.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
#include "handlers.hpp"
|
|
||||||
|
|
||||||
AnimeBot::AnimeBot(const std::string& token)
|
|
||||||
: bot(token)
|
|
||||||
, handler(bot.getApi()) {
|
|
||||||
setupHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimeBot::setupHandlers() {
|
|
||||||
bot.getEvents().onCommand("start", [this](TgBot::Message::Ptr message) {
|
|
||||||
sendMainMenu(message->chat->id);
|
|
||||||
//TODO: производить инициализацию контекста только после авторизации
|
|
||||||
handler.initUser(message->from->id);
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.getEvents().onCallbackQuery([this](TgBot::CallbackQuery::Ptr query) {
|
|
||||||
handler.handleCallback(query);
|
|
||||||
});
|
|
||||||
|
|
||||||
bot.getEvents().onAnyMessage([this](TgBot::Message::Ptr message) {
|
|
||||||
handler.handleMessage(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimeBot::sendMainMenu(int64_t chatId) {
|
|
||||||
auto keyboard = KeyboardFactory::createMainMenu();
|
|
||||||
bot.getApi().sendMessage(chatId, BotConstants::Text::MAIN_MENU, nullptr, nullptr, keyboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
TgBot::Bot& AnimeBot::getBot() {
|
|
||||||
return bot;
|
|
||||||
}
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
#include "handlers.hpp"
|
|
||||||
#include "KeyboardFactory.hpp"
|
|
||||||
#include "structs.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
|
|
||||||
void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query) {
|
|
||||||
//const auto& current = ctx.history.back(); // текущий экран
|
|
||||||
const std::string& data = query->data;
|
|
||||||
int64_t userId = query->from->id;
|
|
||||||
int64_t chatId = query->message->chat->id;
|
|
||||||
int64_t messageId = query->message->messageId;
|
|
||||||
|
|
||||||
// Пагинация (в списках)
|
|
||||||
/* Временно отключаем, все равно не функционирует :)
|
|
||||||
if ((data == BotConstants::Callback::LIST_PREV || data == BotConstants::Callback::LIST_NEXT)
|
|
||||||
&& (current.state == UserState::VIEWING_MY_TITLES || current.state == UserState::VIEWING_REVIEW_LIST ||
|
|
||||||
current.state == UserState::VIEWING_FOUND_TITLES)) {
|
|
||||||
|
|
||||||
int64_t newPayload = current.payload;
|
|
||||||
if (data == BotConstants::Callback::LIST_PREV && newPayload > 0) {
|
|
||||||
reducePayload(newPayload, current.state);
|
|
||||||
} else if (data == BotConstants::Callback::LIST_NEXT) {
|
|
||||||
increasePayload(newPayload, current.state);
|
|
||||||
} else {
|
|
||||||
if (data == BotConstants::Callback::LIST_PREV) {
|
|
||||||
std::cout << "Error: navigation:prev callback for 1st page" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: log
|
|
||||||
std::cout << "Error: navigation:prev unknown error" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.history.back().payload = newPayload;
|
|
||||||
|
|
||||||
renderCurrent(query, ctx);
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Обработка back по интерфейсу
|
|
||||||
if (data == BotConstants::Callback::NAV_BACK) {
|
|
||||||
if (!contextManager.popStep(userId)) {
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
renderCurrent(query);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Переходы вперёд (push)
|
|
||||||
std::optional<NavigationStep> currentStep = contextManager.getCurrentStep(userId);
|
|
||||||
if(!currentStep.has_value()) {
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto newStepOpt = computeNextStep(query, currentStep.value());
|
|
||||||
if (!newStepOpt.has_value()) {
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
contextManager.pushNavigationStep(userId, newStepOpt.value());
|
|
||||||
renderCurrent(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::renderCurrent(TgBot::CallbackQuery::Ptr query) {
|
|
||||||
int64_t userId = query->from->id;
|
|
||||||
int64_t chatId = query->message->chat->id;
|
|
||||||
int64_t messageId = query->message->messageId;
|
|
||||||
|
|
||||||
auto step = contextManager.getCurrentStep(userId);
|
|
||||||
if(!step.has_value()) {;
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::SAD_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (step.value().state) {
|
|
||||||
case UserState::MAIN_MENU:
|
|
||||||
editMessage(chatId, messageId, showMainMenu());
|
|
||||||
return;
|
|
||||||
case UserState::VIEWING_MY_TITLES:
|
|
||||||
server_.fetchUserTitlesAsync(std::to_string(22)) // ALARM: тестовое значение вместо userId
|
|
||||||
.then([this, chatId, messageId](pplx::task<std::vector<BotStructs::Title>> t) {
|
|
||||||
try {
|
|
||||||
auto titles = t.get();
|
|
||||||
|
|
||||||
std::string message = formatTitlesList(titles);
|
|
||||||
auto keyboard = KeyboardFactory::createMyTitles(titles);
|
|
||||||
|
|
||||||
editMessage(chatId, messageId, {message, keyboard});
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::SERVER_ERROR);
|
|
||||||
// Логирование ошибки (например, в cerr)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
case UserState::VIEWING_TITLE_PAGE:
|
|
||||||
server_.fetchTitleAsync(std::to_string(22), step.value().payload)
|
|
||||||
.then([this, chatId, messageId](pplx::task<BotStructs::Title> t) {
|
|
||||||
try {
|
|
||||||
auto title = t.get();
|
|
||||||
|
|
||||||
std::string message = formatTitle(title);
|
|
||||||
auto keyboard = KeyboardFactory::createTitleMenu(title.id);
|
|
||||||
|
|
||||||
std::string imageUrl = "https://i.pinimg.com/736x/30/2b/49/302b49176e5a74ef43871a462df47e1f.jpg";
|
|
||||||
|
|
||||||
editMessageWithPhoto(chatId, messageId, imageUrl, {message, keyboard});
|
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::SERVER_ERROR);
|
|
||||||
// Логирование ошибки (например, в cerr)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
/*
|
|
||||||
case UserState::VIEWING_REVIEW:
|
|
||||||
return returnReview(step.payload); // payload = reviewId
|
|
||||||
case UserState::AWAITING_REVIEW:
|
|
||||||
return HandlerResult{"Пришлите текст отзыва:", nullptr};
|
|
||||||
// ...
|
|
||||||
*/
|
|
||||||
default:
|
|
||||||
return editMessage(chatId, messageId, HandlerResult{BotConstants::Text::SAD_ERROR, nullptr});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<NavigationStep> BotHandlers::computeNextStep(
|
|
||||||
const TgBot::CallbackQuery::Ptr& query,
|
|
||||||
const NavigationStep& current
|
|
||||||
) {
|
|
||||||
const std::string& data = query->data;
|
|
||||||
|
|
||||||
switch (current.state) {
|
|
||||||
case UserState::MAIN_MENU:
|
|
||||||
if (data == BotConstants::Callback::MY_TITLES) {
|
|
||||||
return NavigationStep{UserState::VIEWING_MY_TITLES, 0};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UserState::VIEWING_MY_TITLES:
|
|
||||||
if (data.starts_with(BotConstants::Callback::CHOICE)) {
|
|
||||||
int64_t titleId = parseId(data);
|
|
||||||
return NavigationStep{UserState::VIEWING_TITLE_PAGE, titleId};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
/*
|
|
||||||
case UserState::VIEWING_TITLE_PAGE:
|
|
||||||
if (data == BotConstants::Callback::ACTION_ADD_REVIEW) {
|
|
||||||
return NavigationStep{UserState::AWAITING_REVIEW, current.payload};
|
|
||||||
}
|
|
||||||
if (data.starts_with("review_")) {
|
|
||||||
int64_t reviewId = parseId(data);
|
|
||||||
return NavigationStep{UserState::VIEWING_REVIEW, reviewId};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
*/
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string BotHandlers::formatTitlesList(const std::vector<BotStructs::Title>& titles) {
|
|
||||||
if (titles.empty()) {
|
|
||||||
return "У вас пока нет тайтлов.";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string msg;
|
|
||||||
for (size_t i = 0; i < titles.size(); ++i) {
|
|
||||||
// num — 0-based, но в сообщении показываем 1-based
|
|
||||||
msg += std::to_string(i + 1) + ". " + titles[i].name + "\n";
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string BotHandlers::formatTitle(const BotStructs::Title& title) {
|
|
||||||
std::string msg;
|
|
||||||
msg += title.name + "\n";
|
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t BotHandlers::parseId(const std::string& data) {
|
|
||||||
const std::string prefix = "choice:";
|
|
||||||
if (data.substr(0, prefix.size()) != prefix) {
|
|
||||||
throw std::invalid_argument("Data does not start with 'choice:'");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string idPart = data.substr(prefix.size());
|
|
||||||
|
|
||||||
// Проверяем, что остаток состоит только из цифр (и, возможно, знака '-')
|
|
||||||
if (idPart.empty()) {
|
|
||||||
throw std::invalid_argument("ID part is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t startPos = 0;
|
|
||||||
if (idPart[0] == '-') {
|
|
||||||
if (idPart.size() == 1) {
|
|
||||||
throw std::invalid_argument("Invalid negative ID");
|
|
||||||
}
|
|
||||||
startPos = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = startPos; i < idPart.size(); ++i) {
|
|
||||||
if (!std::isdigit(static_cast<unsigned char>(idPart[i]))) {
|
|
||||||
throw std::invalid_argument("ID contains non-digit characters");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return std::stoll(idPart);
|
|
||||||
} catch (const std::out_of_range&) {
|
|
||||||
throw std::out_of_range("ID is out of range for int64_t");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
#include "handlers.hpp"
|
|
||||||
#include "KeyboardFactory.hpp"
|
|
||||||
#include "structs.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
|
|
||||||
void BotHandlers::handleCallback(TgBot::CallbackQuery::Ptr query) {
|
|
||||||
if (!query) {
|
|
||||||
// TODO: log
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: Тут mutex на многопоточке
|
|
||||||
botApi.answerCallbackQuery(query->id);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "answerCallbackQuery error";
|
|
||||||
//TODO: обработка ошибки
|
|
||||||
}
|
|
||||||
|
|
||||||
processCallbackImpl(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* deprecated. will be deleted soon.
|
|
||||||
HandlerResult BotHandlers::returnMyTitles(int64_t userId, int64_t payload) {
|
|
||||||
// Здесь должен происходить запрос на сервер
|
|
||||||
std::vector<BotStructs::Title> titles = {{123, "Школа мертвяков", "", 1}, {321, "KissXsis", "", 2}};
|
|
||||||
|
|
||||||
struct HandlerResult result;
|
|
||||||
result.keyboard = KeyboardFactory::createMyTitles(titles);
|
|
||||||
result.message = "1. Школа мертвяков\n2. KissXsis\n";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
void BotHandlers::handleMessage(TgBot::Message::Ptr message) {
|
|
||||||
//TODO: просмотр состояния пользователя
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::processCallbackImpl(TgBot::CallbackQuery::Ptr query) {
|
|
||||||
const std::string& data = query->data;
|
|
||||||
int64_t userId = query->from->id;
|
|
||||||
int64_t chatId = query->message->chat->id;
|
|
||||||
int64_t messageId = query->message->messageId;
|
|
||||||
|
|
||||||
std::optional<UserContext> ctx = contextManager.getContext(userId);
|
|
||||||
if (!ctx.has_value()) {
|
|
||||||
// TODO: log
|
|
||||||
sendError(chatId, messageId, BotConstants::Text::AUTH_ERROR);
|
|
||||||
std::cout << "Error: Не нашел пользователя " << userId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.starts_with(BotConstants::Callback::NAVIGATION)) {
|
|
||||||
handleNavigation(query);
|
|
||||||
}
|
|
||||||
else if (data.starts_with(BotConstants::Callback::CHOICE)) {
|
|
||||||
handleNavigation(query);
|
|
||||||
}
|
|
||||||
else if (data.starts_with(BotConstants::Callback::ERROR)) {
|
|
||||||
handleError(query);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
botApi.sendMessage(query->message->chat->id, BotConstants::Text::SAD_ERROR, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::reducePayload(int64_t& payload, const UserState curState) {
|
|
||||||
if (curState == UserState::VIEWING_MY_TITLES ||
|
|
||||||
curState == UserState::VIEWING_FOUND_TITLES) {
|
|
||||||
payload -= BotConstants::DISP_TITLES_NUM;
|
|
||||||
} else if (curState == UserState::VIEWING_REVIEW_LIST) {
|
|
||||||
payload -= BotConstants::DISP_REVIEW_NUM;
|
|
||||||
} else {
|
|
||||||
// TODO: log
|
|
||||||
payload = BotConstants::NULL_PAYLOAD;
|
|
||||||
std::cerr << "Error: reducePayload" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::increasePayload(int64_t& payload, const UserState curState) {
|
|
||||||
if (curState == UserState::VIEWING_MY_TITLES ||
|
|
||||||
curState == UserState::VIEWING_FOUND_TITLES) {
|
|
||||||
payload += BotConstants::DISP_TITLES_NUM;
|
|
||||||
} else if (curState == UserState::VIEWING_REVIEW_LIST) {
|
|
||||||
payload += BotConstants::DISP_REVIEW_NUM;
|
|
||||||
} else {
|
|
||||||
// TODO: log
|
|
||||||
payload = BotConstants::NULL_PAYLOAD;
|
|
||||||
std::cerr << "Error: increasePayload" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::editMessage(int64_t chatId, int64_t messageId, HandlerResult response) {
|
|
||||||
// botApi.editMessageText быстрее. Реализовать его, где возможно
|
|
||||||
try {
|
|
||||||
botApi.deleteMessage(chatId, messageId);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Warning: Failed to delete message " << messageId
|
|
||||||
<< " in chat " << chatId << ": " << e.what() << std::endl;
|
|
||||||
// Продолжаем отправку нового сообщения даже при ошибке удаления
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Отправляем новое сообщение с правильным порядком параметров
|
|
||||||
botApi.sendMessage(
|
|
||||||
chatId, // chatId
|
|
||||||
response.message, // text
|
|
||||||
nullptr, // linkPreviewOptions (nullptr = default)
|
|
||||||
nullptr, // replyParameters (не отвечаем на сообщение)
|
|
||||||
response.keyboard, // replyMarkup — клавиатура
|
|
||||||
"HTML", // parseMode — поддержка <b>, <i> и т.д.
|
|
||||||
false // disableNotification
|
|
||||||
// остальные параметры по умолчанию
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::editMessageWithPhoto(
|
|
||||||
int64_t chatId,
|
|
||||||
int64_t messageId,
|
|
||||||
const std::string& imageUrl,
|
|
||||||
HandlerResult response)
|
|
||||||
{
|
|
||||||
// 1. Удаляем старое сообщение
|
|
||||||
try {
|
|
||||||
botApi.deleteMessage(chatId, messageId);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
// Игнорируем ошибку, если сообщение уже удалено или недоступно
|
|
||||||
std::cerr << "Warning: Failed to delete message: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string safeCaption = response.message;
|
|
||||||
if (safeCaption.length() > 1024) {
|
|
||||||
safeCaption = safeCaption.substr(0, 1021) + "...";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка фото по URL
|
|
||||||
botApi.sendPhoto(
|
|
||||||
chatId,
|
|
||||||
imageUrl,
|
|
||||||
safeCaption,
|
|
||||||
nullptr,
|
|
||||||
response.keyboard,
|
|
||||||
"HTML"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerResult BotHandlers::showMainMenu() {
|
|
||||||
auto keyboard = KeyboardFactory::createMainMenu();
|
|
||||||
|
|
||||||
return HandlerResult{BotConstants::Text::MAIN_MENU, keyboard};
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::sendError(int64_t chatId, int64_t messageId, const std::string& errText) {
|
|
||||||
//TODO: посылать сообщение с кнопкой возврата в главное меню
|
|
||||||
TgBot::InlineKeyboardMarkup::Ptr keyboard;
|
|
||||||
if (errText == BotConstants::Text::SAD_ERROR) {
|
|
||||||
keyboard = KeyboardFactory::createError(BotConstants::Callback::ERROR_NAVIGATION);
|
|
||||||
}
|
|
||||||
else if (errText == BotConstants::Text::AUTH_ERROR) {
|
|
||||||
keyboard = nullptr; //KeyboardFactory::createError(BotConstants::Callback::ERROR_AUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
editMessage(chatId, messageId, {errText, keyboard});
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::handleError(TgBot::CallbackQuery::Ptr query) {
|
|
||||||
const std::string& data = query->data;
|
|
||||||
int64_t userId = query->from->id;
|
|
||||||
int64_t chatId = query->message->chat->id;
|
|
||||||
int64_t messageId = query->message->messageId;
|
|
||||||
|
|
||||||
if(data == BotConstants::Callback::ERROR_NAVIGATION) {
|
|
||||||
contextManager.removeContext(userId);
|
|
||||||
contextManager.createInitContext(userId);
|
|
||||||
auto result = showMainMenu();
|
|
||||||
editMessage(chatId, messageId, result);
|
|
||||||
}
|
|
||||||
else if(data == BotConstants::Callback::ERROR_AUTH) {
|
|
||||||
// TODO: продумать логику
|
|
||||||
HandlerResult result = {BotConstants::Text::AUTH_ERROR, nullptr};
|
|
||||||
editMessage(chatId, messageId, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BotHandlers::initUser(int64_t userId) {
|
|
||||||
contextManager.createInitContext(userId);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#include <front.hpp>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
AnimeBot bot(getenv("TOKEN"));
|
|
||||||
TgBot::TgLongPoll longPoll(bot.getBot());
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
longPoll.start();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << "Error: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -28,6 +28,16 @@ server {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_cache_bypass $http_upgrade;
|
proxy_cache_bypass $http_upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /media/ {
|
||||||
|
rewrite ^/media/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://nyanimedb-images:8000/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
#error_page 404 /404.html;
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import UserPage from "./pages/UserPage/UserPage";
|
import UserPage from "./pages/UserPage/UserPage";
|
||||||
import TitlesPage from "./pages/TitlesPage/TitlesPage";
|
import TitlesPage from "./pages/TitlesPage/TitlesPage";
|
||||||
|
|
@ -11,12 +12,26 @@ import { Header } from "./components/Header/Header";
|
||||||
// OpenAPI.WITH_CREDENTIALS = true
|
// OpenAPI.WITH_CREDENTIALS = true
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const username = localStorage.getItem("username") || undefined;
|
const [userId, setUserId] = useState<string | null>(localStorage.getItem("user_id"));
|
||||||
const userId = localStorage.getItem("userId");
|
|
||||||
|
// 2. Listen for the same event the Header uses
|
||||||
|
useEffect(() => {
|
||||||
|
const handleAuthChange = () => {
|
||||||
|
setUserId(localStorage.getItem("user_id"));
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("storage", handleAuthChange);
|
||||||
|
window.addEventListener("local-storage-update", handleAuthChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("storage", handleAuthChange);
|
||||||
|
window.removeEventListener("local-storage-update", handleAuthChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Header username={username} />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* auth */}
|
{/* auth */}
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import type { Client, Options as Options2, TDataShape } from './client';
|
import type { Client, Options as Options2, TDataShape } from './client';
|
||||||
import { client } from './client.gen';
|
import { client } from './client.gen';
|
||||||
import type { AddUserTitleData, AddUserTitleErrors, AddUserTitleResponses, DeleteUserTitleData, DeleteUserTitleErrors, DeleteUserTitleResponses, GetTitleData, GetTitleErrors, GetTitleResponses, GetTitlesData, GetTitlesErrors, GetTitlesResponses, GetUsersData, GetUsersErrors, GetUsersIdData, GetUsersIdErrors, GetUsersIdResponses, GetUsersResponses, GetUserTitleData, GetUserTitleErrors, GetUserTitleResponses, GetUserTitlesData, GetUserTitlesErrors, GetUserTitlesResponses, UpdateUserData, UpdateUserErrors, UpdateUserResponses, UpdateUserTitleData, UpdateUserTitleErrors, UpdateUserTitleResponses } from './types.gen';
|
import type { AddUserTitleData, AddUserTitleErrors, AddUserTitleResponses, DeleteUserTitleData, DeleteUserTitleErrors, DeleteUserTitleResponses, GetTitleData, GetTitleErrors, GetTitleResponses, GetTitlesData, GetTitlesErrors, GetTitlesResponses, GetUsersData, GetUsersErrors, GetUsersIdData, GetUsersIdErrors, GetUsersIdResponses, GetUsersResponses, GetUserTitleData, GetUserTitleErrors, GetUserTitleResponses, GetUserTitlesData, GetUserTitlesErrors, GetUserTitlesResponses, PostMediaUploadData, PostMediaUploadErrors, PostMediaUploadResponses, UpdateUserData, UpdateUserErrors, UpdateUserResponses, UpdateUserTitleData, UpdateUserTitleErrors, UpdateUserTitleResponses } from './types.gen';
|
||||||
|
|
||||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,3 +113,18 @@ export const updateUserTitle = <ThrowOnError extends boolean = false>(options: O
|
||||||
...options.headers
|
...options.headers
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload an image (PNG, JPEG, or WebP)
|
||||||
|
*
|
||||||
|
* Uploads a single image file. Supported formats: **PNG**, **JPEG/JPG**, **WebP**.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const postMediaUpload = <ThrowOnError extends boolean = false>(options: Options<PostMediaUploadData, ThrowOnError>) => (options.client ?? client).post<PostMediaUploadResponses, PostMediaUploadErrors, ThrowOnError>({
|
||||||
|
url: '/media/upload',
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'encoding',
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export type UserTitle = {
|
||||||
status: UserTitleStatus;
|
status: UserTitleStatus;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
review_id?: number;
|
review_id?: number;
|
||||||
ctime?: string;
|
ftime?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserTitleMini = {
|
export type UserTitleMini = {
|
||||||
|
|
@ -134,7 +134,7 @@ export type UserTitleMini = {
|
||||||
status: UserTitleStatus;
|
status: UserTitleStatus;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
review_id?: number;
|
review_id?: number;
|
||||||
ctime?: string;
|
ftime?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Review = {
|
export type Review = {
|
||||||
|
|
@ -453,6 +453,7 @@ export type AddUserTitleData = {
|
||||||
title_id: number;
|
title_id: number;
|
||||||
status: UserTitleStatus;
|
status: UserTitleStatus;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
|
ftime?: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
/**
|
/**
|
||||||
|
|
@ -578,6 +579,7 @@ export type UpdateUserTitleData = {
|
||||||
body: {
|
body: {
|
||||||
status?: UserTitleStatus;
|
status?: UserTitleStatus;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
|
ftime?: string;
|
||||||
};
|
};
|
||||||
path: {
|
path: {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
|
|
@ -618,3 +620,38 @@ export type UpdateUserTitleResponses = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateUserTitleResponse = UpdateUserTitleResponses[keyof UpdateUserTitleResponses];
|
export type UpdateUserTitleResponse = UpdateUserTitleResponses[keyof UpdateUserTitleResponses];
|
||||||
|
|
||||||
|
export type PostMediaUploadData = {
|
||||||
|
body: unknown;
|
||||||
|
path?: never;
|
||||||
|
query?: never;
|
||||||
|
url: '/media/upload';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PostMediaUploadErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request — e.g., invalid/malformed image, empty file
|
||||||
|
*/
|
||||||
|
400: string;
|
||||||
|
/**
|
||||||
|
* Unsupported Media Type — e.g., request `Content-Type` is not `multipart/form-data`,
|
||||||
|
* or the `image` part has an unsupported `Content-Type` (not image/png, image/jpeg, or image/webp)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
415: unknown;
|
||||||
|
/**
|
||||||
|
* Internal server error
|
||||||
|
*/
|
||||||
|
500: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PostMediaUploadError = PostMediaUploadErrors[keyof PostMediaUploadErrors];
|
||||||
|
|
||||||
|
export type PostMediaUploadResponses = {
|
||||||
|
/**
|
||||||
|
* Image uploaded successfully
|
||||||
|
*/
|
||||||
|
200: Image;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PostMediaUploadResponse = PostMediaUploadResponses[keyof PostMediaUploadResponses];
|
||||||
|
|
|
||||||
16
modules/frontend/src/auth/client.gen.ts
Normal file
16
modules/frontend/src/auth/client.gen.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
|
import { type ClientOptions, type Config, createClient, createConfig } from './client';
|
||||||
|
import type { ClientOptions as ClientOptions2 } from './types.gen';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `createClientConfig()` function will be called on client initialization
|
||||||
|
* and the returned object will become the client's initial configuration.
|
||||||
|
*
|
||||||
|
* You may want to initialize your client this way instead of calling
|
||||||
|
* `setConfig()`. This is useful for example if you're using Next.js
|
||||||
|
* to ensure your client always has the correct values.
|
||||||
|
*/
|
||||||
|
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
|
||||||
|
|
||||||
|
export const client = createClient(createConfig<ClientOptions2>({ baseUrl: '/auth' }));
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue