Compare commits
No commits in common. "master" and "FEATURE/forced-transfer-mode" have entirely different histories.
master
...
FEATURE/fo
133 changed files with 1862 additions and 11245 deletions
48
.env.example
48
.env.example
|
|
@ -1,48 +0,0 @@
|
||||||
# No .env.example was previously tracked in this repo (.env is gitignored).
|
|
||||||
# This file documents every var read via decouple's config() in main/settings.py.
|
|
||||||
# Copy to .env and fill in real values before running the service.
|
|
||||||
|
|
||||||
SECRET_KEY=
|
|
||||||
DEBUG=True
|
|
||||||
|
|
||||||
DB_NAME=
|
|
||||||
DB_USER=
|
|
||||||
DB_PASSWORD=
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=5432
|
|
||||||
|
|
||||||
OAUTH2_PROVIDER_PUBLIC_URL=
|
|
||||||
OAUTH2_PROVIDER_PRIVATE_URL=
|
|
||||||
OAUTH2_CLIENT_ID=
|
|
||||||
OAUTH2_CLIENT_SECRET=
|
|
||||||
OAUTH2_SCOPES=
|
|
||||||
|
|
||||||
REDIS_BASE_URL=
|
|
||||||
LOKI_BASE_PUBLIC_URL=
|
|
||||||
|
|
||||||
MINIO_ENDPOINT=drive.gooyal.com
|
|
||||||
MINIO_USE_HTTPS=True
|
|
||||||
MINIO_EXTERNAL_ENDPOINT=drive.gooyal.com
|
|
||||||
MINIO_EXTERNAL_ENDPOINT_USE_HTTPS=True
|
|
||||||
MINIO_ACCESS_KEY=
|
|
||||||
MINIO_SECRET_KEY=
|
|
||||||
MINIO_MEDIA_FILES_BUCKET=
|
|
||||||
|
|
||||||
ACCOUNTS_BASE_PUBLIC_URL=
|
|
||||||
|
|
||||||
WALLET_BASE_PUBLIC_URL=
|
|
||||||
WALLET_RIAL_DEPOSIT=
|
|
||||||
WALLET_USER_BILLBOARD_VISIT_INCOME=
|
|
||||||
|
|
||||||
# NEW — wallet-naming refactor (2026-08-25), matches advertising/settlement/ipg.
|
|
||||||
# Do not reuse WALLET_RIAL_DEPOSIT or WALLET_USER_BILLBOARD_VISIT_INCOME here — this must
|
|
||||||
# be a distinct, dedicated platform wallet the wallet-service team provisions for promotions.
|
|
||||||
# TODO(you): replace with the real wallet-type UUID from the wallet service.
|
|
||||||
WALLET_PROMOTIONS_CREDIT=00000000-0000-0000-0000-000000000000
|
|
||||||
|
|
||||||
# NEW — per-promotion-type wallet routing (2026-08-25). Same wallet-service UUID as
|
|
||||||
# advertising's own WALLET_ADVERTISING_TRANSIT setting — copy that repo's real value here,
|
|
||||||
# don't provision a second one.
|
|
||||||
WALLET_ADVERTISING_TRANSIT=00000000-0000-0000-0000-000000000000
|
|
||||||
|
|
||||||
NOTIFICATIONS_BASE_PUBLIC_URL=
|
|
||||||
326
README.md
326
README.md
|
|
@ -1,326 +0,0 @@
|
||||||
# Promotions
|
|
||||||
|
|
||||||
**Gooyal platform — service brief**
|
|
||||||
|
|
||||||
The reward-payout engine: it turns "a user did X" events from other Gooyal apps into real wallet deposits. This is a map of how it works internally and how it fits into the rest of the platform, written for the next person extending it.
|
|
||||||
|
|
||||||
`apps/promotions` · Django 5.1 · DRF · Postgres · Celery/Redis
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## TL;DR
|
|
||||||
|
|
||||||
- **It's a rules engine, not a payment processor.** Other Gooyal apps report *events* ("user created their first ad"); promotions matches them against configured *plans* and decides who gets paid what.
|
|
||||||
- **It never moves money itself.** Every payout is a deposit submitted to the separate **wallet** service, then verified. Promotions only tracks whether that deposit succeeded.
|
|
||||||
- **Identity is borrowed, not owned.** Users and OAuth2 applications are lazily mirrored here from the central **accounts** service the moment they're first seen.
|
|
||||||
- **Two front doors, one engine.** A user-token API (self-service) and an application-token API (server-to-server, keyed by `user_uuid`) both funnel into the same `Plan` / `Recipient` / `Promotion` models.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## System map
|
|
||||||
|
|
||||||
Gooyal is a constellation of small Django services (accounts, wallet, notifications, advertising, chat, campaign, settlement, ipg…) that all authenticate through one central OAuth2 provider. Promotions is a consumer-facing resource server that in turn calls three of its siblings.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
classDef svc fill:#3550d6,stroke:#3550d6,color:#fff,rx:6,ry:6
|
|
||||||
classDef ext fill:#2e7d93,stroke:#2e7d93,color:#fff,rx:6,ry:6
|
|
||||||
classDef infra fill:#eeeeee,stroke:#999,color:#333,rx:6,ry:6
|
|
||||||
classDef caller fill:#ffffff,stroke:#888,color:#333,rx:6,ry:6
|
|
||||||
|
|
||||||
subgraph Callers[" calling applications, each with its own client-credentials token "]
|
|
||||||
direction TB
|
|
||||||
ADV["advertising"]:::caller
|
|
||||||
CHAT["chat"]:::caller
|
|
||||||
CAMP["campaign"]:::caller
|
|
||||||
SET["settlement"]:::caller
|
|
||||||
end
|
|
||||||
|
|
||||||
PROMO["promotions\n(this service)"]:::svc
|
|
||||||
|
|
||||||
ADV --> PROMO
|
|
||||||
CHAT --> PROMO
|
|
||||||
CAMP --> PROMO
|
|
||||||
SET --> PROMO
|
|
||||||
|
|
||||||
PROMO -- "introspect bearer token\n+ fetch user profile" --> ACC["accounts\nOAuth2 provider + identity\n(external, not in this checkout)"]:::ext
|
|
||||||
PROMO -- "submit + verify deposit" --> WAL["wallet\napplication & user ledgers\n(sibling repo)"]:::svc
|
|
||||||
PROMO -- "push notification\n(fire-and-forget)" --> NOT["notifications\n(external, not in this checkout)"]:::ext
|
|
||||||
PROMO -- "queue analyze_event_task" --> RED[("Redis")]:::infra
|
|
||||||
RED -- "Celery worker" --> PROMO
|
|
||||||
PROMO --- PG[("Postgres")]:::infra
|
|
||||||
```
|
|
||||||
|
|
||||||
Solid arrows are outbound REST calls made by promotions via generated OpenAPI clients in `utils/clients/`. Blue = Django services; teal = platform services whose source isn't checked out next to this repo.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Identity & auth
|
|
||||||
|
|
||||||
Every request is a borrowed identity. Promotions has no login form and no password table that matters. It's a pure OAuth2 *resource server*: the **accounts** service is the one real authorization server on the platform, and every sibling service — promotions included — just validates tokens against it.
|
|
||||||
|
|
||||||
The `apps/gooyal_oauth2` app looks like a full OAuth2 provider (it defines `Application`, `AccessToken`, `Grant`, `RefreshToken`, `IDToken` models) but that's boilerplate shared across every Gooyal service, not a second identity system. In practice it's used as a **local cache**: `apps/gooyal_oauth2/validators.py` overrides django-oauth-toolkit's introspection so that the first time a bearer token is seen, promotions POSTs it to accounts's `/oauth2/introspect/`, then a second time to `/oauth2/introspect_application/`, and materializes the result as local `Application` / `AccessToken` rows. That's what lets `Plan.application` and `Promotion.application` be plain foreign keys instead of remote lookups on every access.
|
|
||||||
|
|
||||||
`apps/users.User` works the same way: a slim shadow row (UUID primary key, almost no other fields) created on first contact — either by the introspection validator (`get_or_create_user_from_content`) or explicitly in the application-token views, which call `utils/clients/accounts_client.get_user_info()` when a `user_uuid` in the URL doesn't have a local row yet.
|
|
||||||
|
|
||||||
Authorization itself is scope-based, checked per DRF view via two custom permission classes in `apps/gooyal_oauth2/rest_framework.py`: `IsAuthenticatedOrTokenMatchesOASRequirements` (plain endpoints) and `TokenMatchesViewSetActions` (viewsets, matches scopes per `action` rather than per HTTP method). Real scopes in use, pulled from the test suite:
|
|
||||||
|
|
||||||
| Scope | Grants |
|
|
||||||
|---|---|
|
|
||||||
| `promotions.application.event:submit` | App-to-app: submit an event on a user's behalf |
|
|
||||||
| `promotions.application.user-plan:promote` | App-to-app: trigger promotion evaluation for a plan directly |
|
|
||||||
| `promotions.application.user-plan:list-retrieve` | App-to-app: read plan/recipient configuration |
|
|
||||||
| `promotions.application.user-promotions:promote` | App-to-app: create promotions via `ApplicationPromoteUserApiView` |
|
|
||||||
| `promotions.application.user-promotions:list-retrieve` | App-to-app: list a user's promotion history |
|
|
||||||
| `promotions.user.self-plans:list-retrieve` | End user (session or user-token): browse their own plans |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Domain model
|
|
||||||
|
|
||||||
Everything funnels through `apps/promotions/models.py`. It's compact — five real models — but `Recipient` carries a hand-rolled mini query language that's easy to misread the first time.
|
|
||||||
|
|
||||||
| Model | Role |
|
|
||||||
|---|---|
|
|
||||||
| `EventSaver` | Registers a valid `event_label` and whether it may only ever fire once per user (`save_once`). Acts as the allow-list for incoming events. |
|
|
||||||
| `Event` | One occurrence: `user` (raw UUID, not FK), `application`, `label`, free-form `data` JSON. GIN-indexed on `data`. |
|
|
||||||
| `Plan` | A campaign: a `balance`, the list of event labels that trigger it (`event_list`, GIN-indexed), a banner/description for display, and one or more `Recipient`s. |
|
|
||||||
| `Recipient` | Who gets paid from a `Plan` and how much — expressed as two small DSL strings rather than fixed fields (below). |
|
|
||||||
| `Promotion` | One payout attempt: state machine, links back to the triggering `Event`/`Plan`/`Recipient`, drives the real wallet deposit. |
|
|
||||||
|
|
||||||
### The recipient DSL
|
|
||||||
|
|
||||||
Two `Recipient` fields are tiny expression languages evaluated against whichever `Event` and `Plan` triggered the check.
|
|
||||||
|
|
||||||
`recipient_uuid_field` — who gets paid:
|
|
||||||
|
|
||||||
| Form | Meaning |
|
|
||||||
|---|---|
|
|
||||||
| `<a literal UUID>` | Fixed recipient, e.g. a promo operator account. |
|
|
||||||
| `->event:user` | The most common case: pay the user who fired the event. (`event.user` is read directly — no filter clause before the `->`.) |
|
|
||||||
| `model:key=value&…->model:key` | Conditional form: only resolves if every `key=value` check against `event`/`plan` (or their `data` JSON via `data__key`) matches; then reads the recipient off the named object. |
|
|
||||||
| `QS:Model:key=value&…->field` | Looks the *most recent* matching `Event`/`Plan`/`Promotion` row up by filter, then returns `field` as a literal UUID if it parses as one, else `row.data[field]`. |
|
|
||||||
|
|
||||||
`base_amount_field` is simpler: either a raw integer literal, or `event:key` / `plan:key` to pull the base amount out of that object's `data` JSON. `Recipient.data` then layers `promotion_percentage` (default 100) and an optional `max_promotion_amount` cap on top — see the watch list below for a live bug in that capping path.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Request flow
|
|
||||||
|
|
||||||
From event to money in a wallet. The generic, asynchronous path: an app submits an event, promotions figures out the rest on a Celery worker. (The synchronous "promote this specific plan right now" endpoints skip the queue but land on the same `Recipient.promote()` call.)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant App as Calling app
|
|
||||||
participant Promo as promotions (API)
|
|
||||||
participant Q as Celery / Redis
|
|
||||||
participant Wal as wallet
|
|
||||||
participant Notif as notifications
|
|
||||||
|
|
||||||
App->>Promo: POST /api/v1/events/submit<br/>{label, data} + Bearer token
|
|
||||||
Promo->>Promo: EventSaver.save_event() → Event row
|
|
||||||
Promo-->>App: 201 Created
|
|
||||||
Promo->>Q: analyze_event_task.delay(event.uuid)
|
|
||||||
Q->>Promo: Event.analyze()
|
|
||||||
Promo->>Promo: Plan.objects.related_to_event(event)
|
|
||||||
loop each matching Plan
|
|
||||||
Promo->>Promo: Recipient.promote(plan, event)
|
|
||||||
Promo->>Promo: resolve DSL → recipient uuid + amount
|
|
||||||
Promo->>Promo: Plan.reserve_promotion_amount() (atomic)
|
|
||||||
Promo->>Wal: POST application/<wallet>/deposit/
|
|
||||||
Wal-->>Promo: transaction uuid, state=PENDING
|
|
||||||
Promo->>Wal: GET .../deposit/<uuid>/verify
|
|
||||||
Wal-->>Promo: state=SUCCESS
|
|
||||||
Promo->>Notif: POST push (best-effort, errors swallowed)
|
|
||||||
Promo->>Promo: Promotion.state → SUCCESS
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
`apps/promotions/tasks.py` · `apps/promotions/models.py` (`Event.analyze`, `Plan.process_event`, `Recipient.promote`, `Promotion.promote`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Payment states
|
|
||||||
|
|
||||||
One state machine, defined twice. `PaymentStateChoices` in promotions and `StateChoices` in wallet share the same integer values (1–7) by convention, not by import — worth knowing before you add a state to one and not the other.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
stateDiagram-v2
|
|
||||||
[*] --> CREATED
|
|
||||||
CREATED --> SUCCESS: amount is 0, or plan has a balance_holder
|
|
||||||
CREATED --> PENDING: deposit submitted to wallet
|
|
||||||
PENDING --> SUCCESS: wallet verify returns state 5
|
|
||||||
PENDING --> FAILED: wallet verify returns a non-success state
|
|
||||||
PENDING --> EXPECTED_FAILURE: submit/verify call itself raised
|
|
||||||
FAILED --> [*]
|
|
||||||
SUCCESS --> [*]
|
|
||||||
EXPECTED_FAILURE --> [*]
|
|
||||||
```
|
|
||||||
|
|
||||||
`EXPECTED_FAILURE` means "we don't actually know" — the HTTP call to wallet errored, so promotions guesses failure but the deposit may have gone through. There's no automated reconciliation job for this state today.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API surface
|
|
||||||
|
|
||||||
Two front doors into the same engine. User-token endpoints live under `/promotions/`; application-token endpoints live under `/api/v2/promotions/application/<user_uuid>/` for callers acting on behalf of a user they hold no session for.
|
|
||||||
|
|
||||||
| Path | View | Auth | Purpose |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `POST /promotions/api/v1/events/submit` | `ApplicationEventSubmitAPIView` | token | Fire-and-forget: save the event, queue `analyze_event_task`. |
|
|
||||||
| `GET /promotions/api/v1/events/<label>/status/` | `ApplicationEventStatusApiView` | token | Has this user already been paid for this event label, and if not, what would they get? |
|
|
||||||
| `POST /promotions/api/v1/plans/<plan>/` | `ApplicationPromoteUserApiView` | token | Synchronous: evaluate one specific plan right now, no queue. |
|
|
||||||
| `GET /promotions/api/v1/plans/<plan>/promotins` | `ApplicationPromotionListApiView` | token | List this user's promotion history for a plan. (Route typo — "promotins" — is load-bearing; don't casually rename.) |
|
|
||||||
| `GET/POST /promotions/api/v2/plans/` | `UserPlanViewSet` | token | Self-service plan browsing (DRF router). |
|
|
||||||
| `POST .../application/<user_uuid>/event/` | `ApplicationEventViewSet` | app | Same as event submit, but the caller supplies `user_uuid` instead of holding the user's own token; lazily creates the shadow `User` via accounts. |
|
|
||||||
| `GET .../application/<user_uuid>/event/<label>/status/` | `ApplicationEventViewSet.status` | app | App-side equivalent of the status check above. |
|
|
||||||
| `GET/POST .../application/<user_uuid>/plan/` | `ApplicationUserPlanViewSet` | app | List plans; `promote` action evaluates one on the user's behalf. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Playbook: adding a new promotion, end to end
|
|
||||||
|
|
||||||
Concrete steps, mirroring the real `first-ad-create` fixture in `apps/promotions/tests.py`. No code changes required for a straightforward percentage-of-nothing flat reward — it's entirely data.
|
|
||||||
|
|
||||||
1. **Register the trigger event.** Create an `EventSaver` row naming the event label the calling app will send, and whether it's once-per-user.
|
|
||||||
|
|
||||||
```python
|
|
||||||
EventSaver.objects.create(
|
|
||||||
event_label="ads::first-ad-create",
|
|
||||||
title="first-ad-create",
|
|
||||||
save_once=True, # duplicate submits raise, don't re-pay
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create the plan.** Fund it with a `balance` and list the event label(s) that should trigger it. `processor` is required by the model but currently has no behavioral effect (see watch list) — `OTHERS` is the safe default.
|
|
||||||
|
|
||||||
```python
|
|
||||||
Plan.objects.create(
|
|
||||||
title="first-ad-create",
|
|
||||||
event_list=["ads::first-ad-create"],
|
|
||||||
balance=30_000_000,
|
|
||||||
processor=ProcessorTypeChoices.OTHERS,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Attach a recipient.** The common case: pay the user who fired the event, a flat amount, no percentage math. Set `wallet_destination` to pick which wallet the payout lands in — `user_reward` (the default) pays into the user's cash-like reward wallet; `advertising_transit` funds billboard/ad credit instead (`WALLET_ADVERTISING_TRANSIT`) — see `Recipient.get_wallet_category_uuid()`. A `wallet_uuid` set directly on the `Recipient` always wins over `wallet_destination`.
|
|
||||||
|
|
||||||
```python
|
|
||||||
Recipient.objects.create(
|
|
||||||
plan=plan,
|
|
||||||
label="first-ad-create",
|
|
||||||
recipient_uuid_field="->event:user",
|
|
||||||
base_amount_field="30000", # literal, or "event:data_key"
|
|
||||||
wallet_destination=WalletDestinationChoices.ADVERTISING_TRANSIT,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Have the calling app submit the event.** Either the async application-token route, or the sync per-plan route if the app wants the result inline.
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/v2/promotions/application/<user_uuid>/event/
|
|
||||||
{ "label": "ads::first-ad-create", "data": {} }
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Watch it resolve.** Async submits fan out through `analyze_event_task` to every `Plan` whose `event_list` matches, each producing a `Promotion` row you can inspect in admin or via the status endpoint.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Watch list
|
|
||||||
|
|
||||||
Know these before you touch the calculation path. Ranked by how badly they'll surprise you, not by file order.
|
|
||||||
|
|
||||||
### 🔴 crash — Setting `max_promotion_amount` breaks amount calculation
|
|
||||||
|
|
||||||
`apps/promotions/models.py` — `Recipient.get_promotion_amount()`, lines ~276–307
|
|
||||||
|
|
||||||
`get_promotion_amount()` returns a single `int` when `max_promotion_amount` is set on `Recipient.data`, but returns a `(base_amount, promotion_amount)` tuple otherwise. Both call sites — `Recipient.promote()` and `Plan.get_configured_promotion_amount()` — unconditionally do `base_amount, promotion_amount = recipient.get_promotion_amount(...)`. The capped branch raises `TypeError: cannot unpack non-iterable int object`.
|
|
||||||
|
|
||||||
**Fix:** never set `max_promotion_amount` on a live `Recipient` today. If you need a cap, make `get_promotion_amount` always return the tuple and clamp `promotion_amount` inside it instead of short-circuiting.
|
|
||||||
|
|
||||||
### 🟠 correctness + perf — `event_list__icontains` is a substring text match, not JSON containment
|
|
||||||
|
|
||||||
`apps/promotions/models.py` — `PlanQuerySet.related_to_event()`, `get_event_status_for_user()`
|
|
||||||
|
|
||||||
Both matching paths filter with `event_list__icontains=event.label`. On a JSONField this casts to text and does a case-insensitive `LIKE` — it does not use the `GinIndex` already defined on `event_list`, and it will false-positive match a plan for `"ads::first-ad-create-v2"` when the incoming label is `"ads::first-ad-create"`.
|
|
||||||
|
|
||||||
**Fix:** use `event_list__contains=[event.label]` (jsonb `@>`), which is both exact and index-backed.
|
|
||||||
|
|
||||||
### ⚪ dead code — the processor/handler abstraction isn't wired up
|
|
||||||
|
|
||||||
`apps/promotions/handlers.py`, `models.py Plan.process_event()`
|
|
||||||
|
|
||||||
`Plan.process_event()` ignores `self.processor` entirely and always calls `promote_all()`. `PercentageHandler` and `ReferralHandler` in `handlers.py` both `return` before their real logic and reference an undefined `self.balance` — they're unreachable and would error if called.
|
|
||||||
|
|
||||||
**Fix:** if a new promotion type needs calculation logic different from the generic percentage-with-cap in `Recipient.get_promotion_amount`, this is the intended seam — but it needs to be actually dispatched from `process_event`, not assumed to work.
|
|
||||||
|
|
||||||
### ⚪ duplication — `views_user.py` and `views_application.py` largely repeat each other
|
|
||||||
|
|
||||||
`apps/promotions/views_user.py`, `views_application.py`
|
|
||||||
|
|
||||||
`ApplicationPromoteUserApiView`, `ApplicationPromotionListApiView`, and `ApplicationEventSubmitAPIView` are near-identical copies across both files. `get_queryset()`/`_resolve_user()` also mutate `self.request.user` as a side effect, which is easy to miss when tracing a bug.
|
|
||||||
|
|
||||||
**Fix:** before adding a third variant of any of these, factor the shared body into a mixin — otherwise a fix applied to one silently doesn't apply to its twin.
|
|
||||||
|
|
||||||
### ⚪ idempotency — `Promotion.get_or_create` keys on computed amounts, not just identity
|
|
||||||
|
|
||||||
`apps/promotions/models.py` — `Recipient.promote()`, lines ~319–326
|
|
||||||
|
|
||||||
`get_or_create(promotion_amount=..., user_uuid=..., event=..., plan=..., recipient=..., base_amount=...)` has no `defaults=`; every field is part of the lookup. If the amount calculation for the same (user, event, plan, recipient) ever produces a different number on a retry — a config change mid-flight, or the bug above getting fixed — you get a second `Promotion` row instead of a duplicate-prevented one.
|
|
||||||
|
|
||||||
**Fix:** the real duplicate guard is `EventSaver.save_once` one layer up; don't rely on this `get_or_create` for idempotency if you change how amounts are computed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File map
|
|
||||||
|
|
||||||
**apps/promotions — the engine**
|
|
||||||
|
|
||||||
| Path | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `models.py` | Every entity above, plus the recipient DSL and the payment state machine. |
|
|
||||||
| `handlers.py` | Unwired processor/handler scaffolding — see watch list. |
|
|
||||||
| `views_user.py` / `urls_user.py` | User-token API, mounted at `/promotions/`. |
|
|
||||||
| `views_application.py` / `urls_application.py` | Application-token API, mounted at `/api/v2/promotions/application/<user_uuid>/`. |
|
|
||||||
| `serializers.py` | Thin DRF serializers; business logic stays in models.py. |
|
|
||||||
| `tasks.py` | One Celery task: `analyze_event_task`. |
|
|
||||||
| `tests.py` | APITestCase flows with wallet calls mocked — the best template for testing a new plan. |
|
|
||||||
|
|
||||||
**apps/gooyal_oauth2 & apps/users — borrowed identity**
|
|
||||||
|
|
||||||
| Path | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `gooyal_oauth2/validators.py` | Custom introspection against the central accounts service; materializes local Application/AccessToken rows. |
|
|
||||||
| `gooyal_oauth2/rest_framework.py` | Scope-checking DRF permission classes used across every view. |
|
|
||||||
| `users/models.py` | Shadow `User` — UUID PK, created on first contact, no local auth. |
|
|
||||||
|
|
||||||
**utils/clients — outbound calls to siblings**
|
|
||||||
|
|
||||||
| Path | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| `wallet_client.py` | Deposit submit/verify, withdraw, wallet balance lookups. |
|
|
||||||
| `accounts_client.py` | User profile + application detail lookups. |
|
|
||||||
| `notifications_client.py` | Push + email, both best-effort. |
|
|
||||||
| `gooyal_*_client/` | Generated openapi-python-client SDKs per README.srt — regenerate, don't hand-edit. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Config reference
|
|
||||||
|
|
||||||
Non-secret values only — pulled from `main/settings.py` and this checkout's own `.env`.
|
|
||||||
|
|
||||||
| Variable | Points at |
|
|
||||||
|---|---|
|
|
||||||
| `OAUTH2_PROVIDER_PUBLIC_URL` / `_PRIVATE_URL` | Central accounts service's OAuth2 endpoints (token issuance, introspection). Points at the accounts service's staging environment — see this checkout's own `.env` for the actual hostname. |
|
|
||||||
| `ACCOUNTS_BASE_PUBLIC_URL` | Accounts service REST API (user/application profile reads). |
|
|
||||||
| `WALLET_BASE_PUBLIC_URL` | Wallet service REST API (deposit submit/verify). |
|
|
||||||
| `WALLET_RIAL_DEPOSIT` | User-side "real money" wallet type UUID. Not currently read by any code path in this service — kept for parity with the shared naming convention used across the other Gooyal repos that touch the same wallet-service UUIDs (`advertising`, `settlement`, `ipg`). |
|
|
||||||
| `WALLET_USER_BILLBOARD_VISIT_INCOME` | User-side reward-token wallet type UUID (formerly `WALLET_REWARD`). `payee_wallet` when `Recipient.wallet_destination` is `user_reward` (the default) — see `Recipient.get_wallet_category_uuid()`. |
|
|
||||||
| `WALLET_PROMOTIONS_CREDIT` | Company-side pool payouts are drawn from — the deposit's `payer_wallet` (formerly hardcoded to the same UUID as the payee side; see [`docs/wallet_refactor.md`](docs/wallet_refactor.md)). Needs a real UUID from the wallet-service team before this service can submit a deposit. |
|
|
||||||
| `WALLET_ADVERTISING_TRANSIT` | Same wallet-service UUID as advertising's own setting of the same name. `payee_wallet` when `Recipient.wallet_destination` is `advertising_transit` — billboard/ad credit rather than a user reward — see `Recipient.get_wallet_category_uuid()`. |
|
|
||||||
| `NOTIFICATIONS_BASE_PUBLIC_URL` | Notifications service REST API. |
|
|
||||||
| `OAUTH2_CLIENT_ID` / `_SECRET` / `_SCOPES` | This service's own client-credentials identity, used for every outbound call above via `login_as_client_credentials()` (token cached under the key `promotions_access_token`). |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Compiled by reading `apps/promotions`, `apps/gooyal_oauth2`, `apps/users`, `utils/clients` in this checkout, cross-referenced against the sibling `wallet` repo and the `.env` files of `chat`/`advertising`/`campaign` under `Winsoo/` to confirm which services are real network boundaries versus copy-pasted boilerplate. The `accounts` and `notifications` services are referenced only through their generated API clients — their source isn't checked out locally.*
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements, OAuth2Authentication
|
from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements, OAuth2Authentication
|
||||||
from rest_framework.permissions import (
|
from rest_framework.permissions import (
|
||||||
IsAuthenticated, BasePermission
|
IsAuthenticated
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger("oauth2_provider")
|
logger = logging.getLogger("oauth2_provider")
|
||||||
|
|
@ -26,68 +25,3 @@ class IsAuthenticatedOrTokenMatchesOASRequirements(TokenMatchesOASRequirements):
|
||||||
result = (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view)
|
result = (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view)
|
||||||
logger.debug(f'authentication result: {result}')
|
logger.debug(f'authentication result: {result}')
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class TokenMatchesViewSetActions(BasePermission):
|
|
||||||
"""
|
|
||||||
:attr:action_required_scopes: dict keyed by view set action name with value: iterable action scope lists
|
|
||||||
|
|
||||||
This fulfills the [Open API Specification (OAS; formerly Swagger)](https://www.openapis.org/)
|
|
||||||
list of alternative Security Requirements Objects for oauth2 or openIdConnect:
|
|
||||||
When a list of Security Requirement Objects is defined on the Open API object or Operation Object,
|
|
||||||
only one of Security Requirement Objects in the list needs to be satisfied to authorize the request.
|
|
||||||
[1](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject)
|
|
||||||
|
|
||||||
For each method, a list of lists of allowed scopes is tried in order and the first to match succeeds.
|
|
||||||
|
|
||||||
@example
|
|
||||||
required_action_scopes = {
|
|
||||||
'list': [['read']],
|
|
||||||
'create': [['create1','scope2'], ['alt-scope3'], ['alt-scope4','alt-scope5']],
|
|
||||||
}
|
|
||||||
|
|
||||||
TODO: DRY: subclass TokenHasScope and iterate over values of required_scope?
|
|
||||||
"""
|
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
|
||||||
token = request.auth
|
|
||||||
|
|
||||||
if not token:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if hasattr(token, "scope"): # OAuth 2
|
|
||||||
required_action_scopes = self.get_required_action_scopes(request, view)
|
|
||||||
|
|
||||||
# TODO: use action map instead to analyze method
|
|
||||||
action_map = view.action_map
|
|
||||||
a = view.action
|
|
||||||
|
|
||||||
if a in required_action_scopes:
|
|
||||||
logger.debug(
|
|
||||||
"Required scopes alternatives to access resource: {0}".format(
|
|
||||||
required_action_scopes[a]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for alt in required_action_scopes[a]:
|
|
||||||
if token.is_valid(alt):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.warning("no scope action defined for action {0}".format(a))
|
|
||||||
return False
|
|
||||||
|
|
||||||
assert False, (
|
|
||||||
"TokenMatchesViewSetActions requires the"
|
|
||||||
"`oauth2_provider.rest_framework.OAuth2Authentication` authentication "
|
|
||||||
"class to be used."
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_required_action_scopes(self, request, view):
|
|
||||||
try:
|
|
||||||
return getattr(view, "required_action_scopes")
|
|
||||||
except AttributeError:
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
"TokenMatchesViewSetActions requires the view to"
|
|
||||||
" define the required_action_scopes attribute"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,10 @@ from functools import update_wrapper
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Promotion, Plan, Event, Recipient, EventSaver, AllowedUser, PromotionTransaction
|
from .models import Promotion, Plan, Event
|
||||||
|
|
||||||
class PromotionAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['user_uuid', 'plan', 'promotion_amount', 'recipient', 'event', 'state', 'created_at']
|
|
||||||
|
|
||||||
class PromotionTransactionAdmin(admin.ModelAdmin):
|
admin.site.register(Plan)
|
||||||
list_display = ['user_uuid', 'event_label', 'transaction_type', 'amount', 'state', 'promotion', 'reverses', 'created_at']
|
admin.site.register(Promotion)
|
||||||
list_filter = ['transaction_type', 'state']
|
|
||||||
search_fields = ['user_uuid', 'event_label']
|
|
||||||
|
|
||||||
class PlanAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['uuid', 'title', 'balance']
|
|
||||||
|
|
||||||
class AllowedUserAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['user', 'recipient', 'created_at']
|
|
||||||
|
|
||||||
admin.site.register(Plan, PlanAdmin)
|
|
||||||
admin.site.register(Promotion, PromotionAdmin)
|
|
||||||
admin.site.register(Event)
|
admin.site.register(Event)
|
||||||
admin.site.register(EventSaver)
|
|
||||||
admin.site.register(Recipient)
|
|
||||||
admin.site.register(AllowedUser, AllowedUserAdmin)
|
|
||||||
admin.site.register(PromotionTransaction, PromotionTransactionAdmin)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,30 +8,34 @@ from utils.clients.wallet_client import deposit_to_user_wallet_submit, deposit_t
|
||||||
|
|
||||||
logger = logging.getLogger("promotion_handler")
|
logger = logging.getLogger("promotion_handler")
|
||||||
|
|
||||||
class ProcessorTypeChoices(models.TextChoices):
|
class PromotionTypeChoices(models.TextChoices):
|
||||||
PERCENTAGE = 'percentage', _('percentage')
|
PERCENTAGE = 'percentage', _('percentage')
|
||||||
REFERRAL = 'referral', _('referral')
|
|
||||||
|
|
||||||
OTHERS = 'others', _('others')
|
OTHERS = 'others', _('others')
|
||||||
|
|
||||||
class PromotionTypeChoices(models.TextChoices):
|
@enum_property
|
||||||
pass
|
def handler(self):
|
||||||
|
if self.name == self.PERCENTAGE.name:
|
||||||
|
return PercentageHandler
|
||||||
|
|
||||||
|
elif self.name == self.OTHERS.name:
|
||||||
|
return OtherHandler
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class BasePromotionHandler:
|
class BasePromotionHandler:
|
||||||
def __init__(self, plan):
|
def __init__(self, application=None, policy=None, balance=None):
|
||||||
self.plan = plan
|
self.application = application
|
||||||
|
self.policy = policy
|
||||||
|
self.balance = balance
|
||||||
|
|
||||||
def calculate(self, **kwargs):
|
def calculate(self, **kwargs):
|
||||||
# condition and amount
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def promote(self, **kwargs):
|
def promote(self, **kwargs):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
# def get_recipient_uuid(self, recipient, **kwargs):
|
|
||||||
# raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class PercentageHandler(BasePromotionHandler):
|
class PercentageHandler(BasePromotionHandler):
|
||||||
def calculate(self, **kwargs):
|
def calculate(self, **kwargs):
|
||||||
|
|
@ -44,8 +48,8 @@ class PercentageHandler(BasePromotionHandler):
|
||||||
promotion_amount = 1000 if self.balance >= 1000 else self.balance
|
promotion_amount = 1000 if self.balance >= 1000 else self.balance
|
||||||
return promotion_amount
|
return promotion_amount
|
||||||
|
|
||||||
def promote(self, **kwargs):
|
def promote(self, promotion, **kwargs):
|
||||||
return
|
pass
|
||||||
payee_type = self.policy.get('payee_type')
|
payee_type = self.policy.get('payee_type')
|
||||||
payee_uuid = self.policy.get('payee_uuid')
|
payee_uuid = self.policy.get('payee_uuid')
|
||||||
|
|
||||||
|
|
@ -85,63 +89,5 @@ class PercentageHandler(BasePromotionHandler):
|
||||||
logger.exception('error in finish visit:')
|
logger.exception('error in finish visit:')
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
class ReferralHandler(BasePromotionHandler):
|
|
||||||
def calculate(self, **kwargs):
|
|
||||||
event = kwargs.get('event')
|
|
||||||
plan = self.plan
|
|
||||||
|
|
||||||
|
|
||||||
if base_amount <= 1000 and self.balance >= 1000:
|
|
||||||
promotion_amount = base_amount
|
|
||||||
|
|
||||||
else:
|
|
||||||
promotion_amount = 1000 if self.balance >= 1000 else self.balance
|
|
||||||
return promotion_amount
|
|
||||||
|
|
||||||
def promote(self, **kwargs):
|
|
||||||
return
|
|
||||||
payee_type = self.policy.get('payee_type')
|
|
||||||
payee_uuid = self.policy.get('payee_uuid')
|
|
||||||
|
|
||||||
# promotion_amount = self.calculate(**kwargs)
|
|
||||||
try:
|
|
||||||
data = {
|
|
||||||
"uuid": str(promotion.uuid),
|
|
||||||
"payee_id": str(payee_uuid),
|
|
||||||
"payee_type": payee_type,
|
|
||||||
"amount": promotion.promotion_amount,
|
|
||||||
"details": {
|
|
||||||
'description': "for promotion",
|
|
||||||
'reference_id': str(promotion.uuid),
|
|
||||||
'application_details_url': ''
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
submit_response = deposit_to_user_wallet_submit(data)
|
|
||||||
try:
|
|
||||||
submit_response.uuid
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception('Failed to submit payment')
|
|
||||||
raise Exception('Failed to submit payment')
|
|
||||||
|
|
||||||
verify_response = deposit_to_user_wallet_verify(str(promotion.uuid))
|
|
||||||
logger.debug(f'verify response: {verify_response}')
|
|
||||||
try:
|
|
||||||
if not verify_response.uuid or verify_response.state != 5:
|
|
||||||
logger.debug('Failed to verify payment')
|
|
||||||
raise Exception('Failed to verify payment')
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception('Failed to verify payment')
|
|
||||||
raise Exception('Failed to verify payment')
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception('error in finish visit:')
|
|
||||||
raise e
|
|
||||||
|
|
||||||
# def get_recipient_uuid(self, recipient, **kwargs):
|
|
||||||
# return recipient.uuid
|
|
||||||
|
|
||||||
class OtherHandler(BasePromotionHandler):
|
class OtherHandler(BasePromotionHandler):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.7 on 2025-10-30 10:50
|
# Generated by Django 5.2.4 on 2025-08-02 16:18
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -16,67 +16,18 @@ class Migration(migrations.Migration):
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name='EventSaver',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
|
||||||
('event_label', models.CharField(db_index=True, max_length=255, unique=True, verbose_name='label')),
|
|
||||||
('save_once', models.BooleanField(default=False, verbose_name='save once')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Event',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
|
||||||
('label', models.CharField(db_index=True, max_length=255)),
|
|
||||||
('data', models.JSONField()),
|
|
||||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
|
||||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Plan',
|
name='Plan',
|
||||||
fields=[
|
fields=[
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||||
('event_list', models.JSONField(default=list)),
|
|
||||||
('title', models.CharField(db_index=True, max_length=255, verbose_name='title')),
|
('title', models.CharField(db_index=True, max_length=255, verbose_name='title')),
|
||||||
('description', models.TextField(blank=True, verbose_name='description')),
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
('processor', models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type')),
|
('promotion_type', models.CharField(choices=[('wallet percentage', 'Wallet percentage'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type')),
|
||||||
('data', models.JSONField(blank=True, default=dict, null=True, verbose_name='data')),
|
('policy', models.JSONField(blank=True, default=dict, null=True, verbose_name='policy')),
|
||||||
('balance', models.IntegerField(default=0, verbose_name='balance')),
|
('balance', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='balance')),
|
||||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Recipient',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
|
||||||
('label', models.CharField(db_index=True, max_length=255)),
|
|
||||||
('wallet_uuid', models.UUIDField(blank=True, null=True)),
|
|
||||||
('promotion_type', models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type')),
|
|
||||||
('recipient_uuid_field', models.CharField(db_index=True, max_length=255)),
|
|
||||||
('base_amount_field', models.CharField(db_index=True, max_length=255)),
|
|
||||||
('data', models.JSONField(blank=True, default=dict, null=True)),
|
|
||||||
('plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='recipients', to='promotions.plan')),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
|
@ -88,14 +39,11 @@ class Migration(migrations.Migration):
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||||
('user_uuid', models.UUIDField()),
|
('state', models.IntegerField(choices=[(0, 'Created'), (1, 'Assigned'), (10, 'Expired')], default=0)),
|
||||||
('state', models.IntegerField(choices=[(0, 'Created'), (1, 'Assigned'), (2, 'Promoted'), (10, 'Expired')], default=0)),
|
|
||||||
('base_amount', models.IntegerField(blank=True, null=True)),
|
|
||||||
('promotion_amount', models.IntegerField(blank=True, null=True)),
|
|
||||||
('data', models.JSONField(blank=True, null=True)),
|
('data', models.JSONField(blank=True, null=True)),
|
||||||
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||||
('plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.plan')),
|
('plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.plan')),
|
||||||
('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.recipient')),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-11-06 08:42
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='promotion',
|
|
||||||
name='event',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='promotions', to='promotions.event'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='plan',
|
|
||||||
name='processor',
|
|
||||||
field=models.CharField(choices=[('percentage', 'percentage'), ('referral', 'referral'), ('others', 'others')], db_index=True, max_length=64, verbose_name='processor'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='recipient',
|
|
||||||
name='promotion_type',
|
|
||||||
field=models.CharField(choices=[], db_index=True, max_length=64, verbose_name='promotion type'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.2.4 on 2025-08-03 12:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='promotion',
|
||||||
|
name='promotion_amount',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='promotion',
|
||||||
|
name='state',
|
||||||
|
field=models.IntegerField(choices=[(0, 'Created'), (1, 'Assigned'), (2, 'Promoted'), (10, 'Expired')], default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/promotions/migrations/0003_alter_plan_balance.py
Normal file
18
apps/promotions/migrations/0003_alter_plan_balance.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.2.4 on 2025-08-03 12:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0002_promotion_promotion_amount_alter_promotion_state'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='plan',
|
||||||
|
name='balance',
|
||||||
|
field=models.IntegerField(default=0, verbose_name='balance'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-11-08 16:46
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0002_promotion_event_alter_plan_processor_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='recipient',
|
|
||||||
name='promotion_type',
|
|
||||||
field=models.CharField(blank=True, choices=[], db_index=True, max_length=64, null=True, verbose_name='promotion type'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-11-11 15:24
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0003_alter_recipient_promotion_type'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='promotion',
|
|
||||||
name='state',
|
|
||||||
field=models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure')], default=1),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
18
apps/promotions/migrations/0004_promotion_base_amount.py
Normal file
18
apps/promotions/migrations/0004_promotion_base_amount.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.2.4 on 2025-08-03 12:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0003_alter_plan_balance'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='promotion',
|
||||||
|
name='base_amount',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.2.4 on 2025-11-12 11:27
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("promotions", "0004_alter_promotion_state"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="event",
|
|
||||||
name="user",
|
|
||||||
field=models.UUIDField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
32
apps/promotions/migrations/0005_event_plan_event_list.py
Normal file
32
apps/promotions/migrations/0005_event_plan_event_list.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 5.2.6 on 2025-09-16 17:32
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0004_promotion_base_amount'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Event',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
||||||
|
('label', models.CharField(db_index=True, max_length=255)),
|
||||||
|
('details', models.JSONField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='plan',
|
||||||
|
name='event_list',
|
||||||
|
field=models.JSONField(default=list),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
# Generated by Django 5.1.7 on 2025-11-15 11:19
|
|
||||||
|
|
||||||
import django.contrib.postgres.indexes
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0005_alter_event_user'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='event',
|
|
||||||
index=django.contrib.postgres.indexes.GinIndex(fields=['data'], name='promotions_event_data_bc656abc'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='plan',
|
|
||||||
index=django.contrib.postgres.indexes.GinIndex(fields=['event_list'], name='promotions_plan_event_783bb033'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='plan',
|
|
||||||
index=django.contrib.postgres.indexes.GinIndex(fields=['data'], name='promotions_plan_data_b632bc41'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='promotion',
|
|
||||||
index=django.contrib.postgres.indexes.GinIndex(fields=['data'], name='promotions_promotion_cb76da3e'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='recipient',
|
|
||||||
index=django.contrib.postgres.indexes.GinIndex(fields=['data'], name='promotions_recipient_fba22446'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
18
apps/promotions/migrations/0006_rename_details_event_data.py
Normal file
18
apps/promotions/migrations/0006_rename_details_event_data.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.2.6 on 2025-09-18 11:22
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0005_event_plan_event_list'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='event',
|
||||||
|
old_name='details',
|
||||||
|
new_name='data',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Generated by Django 6.0.2 on 2026-02-22 10:12
|
|
||||||
|
|
||||||
import django_minio_backend.models
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0006_event_promotions_event_data_bc656abc_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='event',
|
|
||||||
options={'ordering': ('-created_at',)},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='plan',
|
|
||||||
name='banner',
|
|
||||||
field=models.ImageField(blank=True, null=True, storage=django_minio_backend.models.MinioBackend(), upload_to=django_minio_backend.models.iso_date_prefix),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='plan',
|
|
||||||
name='description_details',
|
|
||||||
field=models.JSONField(blank=True, default={}, verbose_name='description details'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 5.2.6 on 2025-09-20 11:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0006_rename_details_event_data'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='plan',
|
||||||
|
name='max_amount',
|
||||||
|
field=models.FloatField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='plan',
|
||||||
|
name='min_amount',
|
||||||
|
field=models.FloatField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='plan',
|
||||||
|
name='promotion_type',
|
||||||
|
field=models.CharField(choices=[('percentage', 'percentage'), ('others', 'others')], db_index=True, max_length=64, verbose_name='promotion type'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 5.2.6 on 2025-09-20 12:52
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('promotions', '0007_plan_max_amount_plan_min_amount_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='application',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 5.2.4 on 2026-06-25 06:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0007_alter_event_options_plan_banner_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='plan',
|
|
||||||
name='balance_holder',
|
|
||||||
field=models.UUIDField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='plan',
|
|
||||||
name='description_details',
|
|
||||||
field=models.JSONField(blank=True, default=dict, verbose_name='description details'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('promotions', '0008_plan_balance_holder_alter_plan_description_details'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='recipient',
|
|
||||||
name='access_type',
|
|
||||||
field=models.CharField(choices=[('restricted', 'restricted'), ('public', 'public')],
|
|
||||||
db_index=True, default='public', max_length=64, verbose_name='access type'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='AllowedUser',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True,
|
|
||||||
serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='allowed_recipients', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name='allowed_users', to='promotions.recipient')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'constraints': [
|
|
||||||
models.UniqueConstraint(fields=('user', 'recipient'), name='unique_allowed_user_recipient'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0009_recipient_access_type_alloweduser'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='recipient',
|
|
||||||
name='wallet_destination',
|
|
||||||
field=models.CharField(choices=[('user_reward', 'user reward wallet'),
|
|
||||||
('advertising_transit', 'advertising transit wallet')],
|
|
||||||
db_index=True, default='user_reward', max_length=32,
|
|
||||||
verbose_name='wallet destination'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('promotions', '0010_recipient_wallet_destination'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PromotionTransaction',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
|
|
||||||
('user_uuid', models.UUIDField(db_index=True)),
|
|
||||||
('event_label', models.CharField(db_index=True, max_length=255)),
|
|
||||||
('transaction_type', models.IntegerField(choices=[(1, 'payout'), (2, 'rollback')], db_index=True)),
|
|
||||||
('holder_wallet', models.UUIDField()),
|
|
||||||
('destination_wallet', models.UUIDField()),
|
|
||||||
('amount', models.IntegerField(default=0)),
|
|
||||||
('state', models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'incomplete'), (5, 'success'), (6, 'failed'), (7, 'expected_failure')], db_index=True, default=1)),
|
|
||||||
('external_uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
|
||||||
('promotion', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions', to='promotions.promotion')),
|
|
||||||
('reverses', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reversed_by', to='promotions.promotiontransaction')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['created_at'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddConstraint(
|
|
||||||
model_name='promotiontransaction',
|
|
||||||
constraint=models.UniqueConstraint(fields=('promotion', 'transaction_type'), name='unique_promotion_transaction_type'),
|
|
||||||
),
|
|
||||||
migrations.AddConstraint(
|
|
||||||
model_name='promotiontransaction',
|
|
||||||
constraint=models.UniqueConstraint(condition=models.Q(('transaction_type', 2)), fields=('user_uuid', 'event_label'), name='unique_promotion_rollback'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,68 +1,23 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.postgres.indexes import GinIndex
|
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import F, Q
|
from django.db.models import F
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_minio_backend import MinioBackend, iso_date_prefix
|
|
||||||
|
|
||||||
from oauth2_provider.settings import oauth2_settings
|
from oauth2_provider.settings import oauth2_settings
|
||||||
|
|
||||||
from apps.promotions import handlers
|
from apps.promotions.handlers import PromotionTypeChoices, BasePromotionHandler
|
||||||
import logging
|
|
||||||
from apps.promotions.handlers import ProcessorTypeChoices, BasePromotionHandler, PromotionTypeChoices
|
|
||||||
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from utils.clients.notifications_client import notifications_push_user
|
|
||||||
from utils.clients.wallet_client import deposit_to_user_wallet_submit, deposit_to_user_wallet_verify
|
|
||||||
from utils.models import BaseModel
|
from utils.models import BaseModel
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class EventSaverQuerySet(models.QuerySet):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class EventSaver(BaseModel):
|
|
||||||
title = models.CharField(_('title'), max_length=255)
|
|
||||||
event_label = models.CharField(_('label'), max_length=255, unique=True, db_index=True)
|
|
||||||
save_once = models.BooleanField(_('save once'), default=False)
|
|
||||||
|
|
||||||
objects = EventSaverQuerySet.as_manager()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
def save_event(self, **kwargs):
|
|
||||||
user = kwargs.get('user')
|
|
||||||
application = kwargs.get('application')
|
|
||||||
label = kwargs.get('label')
|
|
||||||
data = kwargs.get('data')
|
|
||||||
|
|
||||||
if self.save_once:
|
|
||||||
event, created = Event.objects.get_or_create(user=user, label=label,
|
|
||||||
defaults={"data": data, "application": application})
|
|
||||||
if created:
|
|
||||||
return event
|
|
||||||
else:
|
|
||||||
# TODO: good exception
|
|
||||||
raise Exception(_('already saved'))
|
|
||||||
|
|
||||||
else:
|
|
||||||
event = Event.objects.create(user=user, application=application, label=label, data=data)
|
|
||||||
return event
|
|
||||||
|
|
||||||
|
|
||||||
class EventQuerySet(models.QuerySet):
|
class EventQuerySet(models.QuerySet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Event(BaseModel):
|
class Event(BaseModel):
|
||||||
user = models.UUIDField(null=True, blank=True)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
related_name='+', null=True, blank=True)
|
related_name='+', null=True, blank=True)
|
||||||
label = models.CharField(max_length=255, db_index=True)
|
label = models.CharField(max_length=255, db_index=True)
|
||||||
|
|
@ -70,407 +25,111 @@ class Event(BaseModel):
|
||||||
|
|
||||||
objects = EventQuerySet.as_manager()
|
objects = EventQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
GinIndex(
|
|
||||||
fields=['data'],
|
|
||||||
# hashlib.md5("promotions_event_data_GinIndex".encode()).hexdigest()[:8] used for hash
|
|
||||||
name='promotions_event_data_bc656abc'
|
|
||||||
),
|
|
||||||
]
|
|
||||||
ordering = ('-created_at',)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.label
|
return self.label
|
||||||
|
|
||||||
def analyze(self):
|
def analyze(self):
|
||||||
for plan in Plan.objects.related_to_event(self):
|
for plan in Plan.objects.get_related_to_event(self):
|
||||||
yield plan.process_event(event=self)
|
print(plan)
|
||||||
|
plan.process_event(self)
|
||||||
|
|
||||||
|
|
||||||
class PlanQuerySet(models.QuerySet):
|
class PlanQuerySet(models.QuerySet):
|
||||||
|
|
||||||
# # this is for list of discount code for example
|
# this is for list of discount code for example
|
||||||
# def calculate(self, user, application=None):
|
def calculate(self, user, application=None):
|
||||||
# for plan in self.filter(application=application)[:10]:
|
for plan in self.filter(application=application)[:10]:
|
||||||
# if not Promotion.objects.used(user):
|
if not Promotion.objects.used(user):
|
||||||
# plan.calculate(user)
|
plan.calculate(user)
|
||||||
|
|
||||||
def related_to_event(self, event):
|
def get_related_to_event(self, event):
|
||||||
return self.filter(event_list__icontains=event.label)
|
return self.filter(event_list__icontains=event.label)
|
||||||
|
|
||||||
|
|
||||||
class Plan(BaseModel):
|
class Plan(BaseModel):
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
# user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, related_name='+',
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
null=True, blank=True)
|
related_name='+', null=True, blank=True)
|
||||||
event_list = models.JSONField(default=list)
|
event_list = models.JSONField(default=list)
|
||||||
title = models.CharField(max_length=255, verbose_name=_('title'), db_index=True)
|
title = models.CharField(max_length=255, verbose_name=_('title'), db_index=True)
|
||||||
banner = models.ImageField(null=True, blank=True, storage=MinioBackend(), upload_to=iso_date_prefix)
|
|
||||||
description = models.TextField(verbose_name=_('description'), blank=True)
|
description = models.TextField(verbose_name=_('description'), blank=True)
|
||||||
description_details = models.JSONField(verbose_name=_('description details'), blank=True, default=dict)
|
promotion_type = models.CharField(max_length=64, verbose_name=_('promotion type'), db_index=True,
|
||||||
processor = models.CharField(max_length=64, verbose_name=_('processor'), db_index=True,
|
choices=PromotionTypeChoices.choices)
|
||||||
choices=ProcessorTypeChoices.choices) # TODO: processor type
|
min_amount = models.FloatField(null=True, blank=True)
|
||||||
data = models.JSONField(default=dict, verbose_name=_('data'), null=True, blank=True)
|
max_amount = models.FloatField(null=True, blank=True)
|
||||||
|
policy = models.JSONField(default=dict, verbose_name=_('policy'), null=True, blank=True)
|
||||||
balance_holder = models.UUIDField(null=True, blank=True)
|
|
||||||
balance = models.IntegerField(verbose_name=_('balance'), default=0)
|
balance = models.IntegerField(verbose_name=_('balance'), default=0)
|
||||||
|
|
||||||
objects = PlanQuerySet.as_manager()
|
objects = PlanQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
GinIndex(
|
|
||||||
fields=['event_list'],
|
|
||||||
# hashlib.md5("promotions_plan_event_list_GinIndex".encode()).hexdigest()[:8] used for hash
|
|
||||||
name='promotions_plan_event_783bb033'
|
|
||||||
),
|
|
||||||
GinIndex(
|
|
||||||
fields=['data'],
|
|
||||||
# hashlib.md5("promotions_plan_data_GinIndex".encode()).hexdigest()[:8] used for hash
|
|
||||||
name='promotions_plan_data_b632bc41'
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
def get_configured_promotion_amount(self, user_uuid=None):
|
@property
|
||||||
access_filter = Q(access_type=RecipientTypeChoices.PUBLIC)
|
def handler(self) -> BasePromotionHandler:
|
||||||
if user_uuid:
|
return PromotionTypeChoices(self.promotion_type).handler(
|
||||||
access_filter |= Q(access_type=RecipientTypeChoices.RESTRICTED, allowed_users__user__uuid=user_uuid)
|
application=self.application,
|
||||||
|
policy=self.policy,
|
||||||
|
balance=self.balance,
|
||||||
|
)
|
||||||
|
|
||||||
recipients = self.recipients.filter(
|
def calculate_promotion(self, **kwargs):
|
||||||
recipient_uuid_field='->event:user' #TODO: instead of hard code use better soloution
|
handler = self.handler
|
||||||
).filter(access_filter).distinct()
|
# self.base_amount = kwargs.get('base_amount', 0)
|
||||||
|
return handler.calculate(**kwargs)
|
||||||
amount = 0
|
|
||||||
for recipient in recipients:
|
|
||||||
base_amount, promotion_amount = recipient.get_promotion_amount()
|
|
||||||
amount += promotion_amount
|
|
||||||
return amount
|
|
||||||
|
|
||||||
def is_promotion_used_by(self, user_uuid):
|
|
||||||
return self.promotions.filter(user_uuid=user_uuid).exclude(
|
|
||||||
state__in=[PaymentStateChoices.FAILED, PaymentStateChoices.EXPECTED_FAILURE]
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
def process_event(self, event=None):
|
|
||||||
# TODO: processor
|
|
||||||
return self.promote_all(event=event)
|
|
||||||
|
|
||||||
def promote_all(self, event=None):
|
|
||||||
for recipient in self.recipients.all():
|
|
||||||
yield recipient.promote(plan=self, event=event)
|
|
||||||
|
|
||||||
def reserve_promotion_amount(self, amount):
|
def reserve_promotion_amount(self, amount):
|
||||||
|
|
||||||
if self.balance_holder:
|
|
||||||
balance_holder_object = Promotion.objects.get(pk=self.balance_holder)
|
|
||||||
balance_field_name = 'promotion_amount'
|
|
||||||
else:
|
|
||||||
balance_holder_object = self
|
|
||||||
balance_field_name = 'balance'
|
|
||||||
|
|
||||||
updated = False
|
updated = False
|
||||||
|
if amount and self.balance >= amount:
|
||||||
select_query_kwargs = {
|
updated = Plan.objects.filter(pk=self.pk, balance__gte=amount).update(
|
||||||
"pk": balance_holder_object.pk,
|
balance=F('balance') - amount
|
||||||
f"{balance_field_name}__gte": amount
|
)
|
||||||
}
|
|
||||||
update_query_kwargs = {
|
|
||||||
balance_field_name: F(balance_field_name) - amount
|
|
||||||
}
|
|
||||||
|
|
||||||
if amount and getattr(balance_holder_object, balance_field_name) >= amount:
|
|
||||||
updated = balance_holder_object._meta.model.objects.filter(**select_query_kwargs).update(
|
|
||||||
**update_query_kwargs)
|
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
|
def process_event(self, event=None):
|
||||||
class RecipientTypeChoices(models.TextChoices):
|
print(event)
|
||||||
RESTRICTED = 'restricted', _('restricted')
|
referral_code = event.data.get('referral_code')
|
||||||
PUBLIC = 'public', _('public')
|
user_uuid = event.data.get('user_uuid')
|
||||||
|
promotion_amount = self.calculate_promotion(**self.policy)
|
||||||
|
if referral_code and promotion_amount and not self.promotions.filter(user_id=referral_code,
|
||||||
class WalletDestinationChoices(models.TextChoices):
|
data__user_uuid=user_uuid).exists():
|
||||||
USER_REWARD = 'user_reward', _('user reward wallet')
|
referral_user, created = User.objects.get_or_create(uuid=referral_code)
|
||||||
ADVERTISING_TRANSIT = 'advertising_transit', _('advertising transit wallet')
|
user = User.objects.get_or_create(uuid=user_uuid)
|
||||||
|
with transaction.atomic():
|
||||||
|
if self.reserve_promotion_amount(promotion_amount):
|
||||||
class Recipient(BaseModel):
|
promotion, created = Promotion.objects.get_or_create(plan=self, user=referral_user, defaults={
|
||||||
label = models.CharField(max_length=255, db_index=True)
|
"data": event.data
|
||||||
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='recipients', null=True, blank=True)
|
})
|
||||||
wallet_uuid = models.UUIDField(null=True, blank=True)
|
if not created:
|
||||||
promotion_type = models.CharField(max_length=64, verbose_name=_('promotion type'), db_index=True,
|
raise Exception('Promotion already reserved')
|
||||||
choices=PromotionTypeChoices.choices, null=True, blank=True)
|
|
||||||
wallet_destination = models.CharField(max_length=32, verbose_name=_('wallet destination'), db_index=True,
|
|
||||||
choices=WalletDestinationChoices.choices,
|
|
||||||
default=WalletDestinationChoices.USER_REWARD)
|
|
||||||
access_type = models.CharField(max_length=64, verbose_name=_('access type'), db_index=True,
|
|
||||||
choices=RecipientTypeChoices.choices, default=RecipientTypeChoices.PUBLIC)
|
|
||||||
|
|
||||||
recipient_uuid_field = models.CharField(max_length=255, db_index=True)
|
|
||||||
base_amount_field = models.CharField(max_length=255, db_index=True)
|
|
||||||
|
|
||||||
data = models.JSONField(null=True, blank=True, default=dict)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
GinIndex(
|
|
||||||
fields=['data'],
|
|
||||||
# hashlib.md5("promotions_recipient_data_GinIndex".encode()).hexdigest()[:8] used for hash
|
|
||||||
name='promotions_recipient_fba22446'
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.label} --> {self.plan}"
|
|
||||||
|
|
||||||
def get_wallet_category_uuid(self):
|
|
||||||
if self.wallet_uuid:
|
|
||||||
return self.wallet_uuid
|
|
||||||
|
|
||||||
if self.wallet_destination == WalletDestinationChoices.ADVERTISING_TRANSIT:
|
|
||||||
return settings.WALLET_ADVERTISING_TRANSIT
|
|
||||||
|
|
||||||
return settings.WALLET_USER_BILLBOARD_VISIT_INCOME
|
|
||||||
|
|
||||||
def is_user_allowed(self, user_uuid):
|
|
||||||
if self.access_type != RecipientTypeChoices.RESTRICTED:
|
|
||||||
return True
|
|
||||||
if not user_uuid:
|
|
||||||
return False
|
|
||||||
return self.allowed_users.filter(user__uuid=user_uuid).exists()
|
|
||||||
|
|
||||||
def get_recipient_uuid(self, plan=None, event=None):
|
|
||||||
if ':' in self.recipient_uuid_field and not self.recipient_uuid_field.startswith('QS:'):
|
|
||||||
filters_key_values, recipient_candidate = self.recipient_uuid_field.split('->')
|
|
||||||
key_values = filters_key_values and filters_key_values.split('&') or []
|
|
||||||
for key_value in key_values:
|
|
||||||
model_name_key, value = key_value.split('=')
|
|
||||||
model_name, key = model_name_key.split(':')
|
|
||||||
if model_name == 'event':
|
|
||||||
obj = event
|
|
||||||
elif model_name == 'plan':
|
|
||||||
obj = plan
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise Exception('Promotion already reserved')
|
||||||
|
promotion.promote()
|
||||||
if key.startswith('data__'):
|
|
||||||
key = key[len('data__'):]
|
|
||||||
obj_value = obj.data.get(key)
|
|
||||||
else:
|
|
||||||
obj_value = getattr(obj, key)
|
|
||||||
|
|
||||||
if obj_value != value:
|
|
||||||
return None
|
|
||||||
|
|
||||||
model_name, key = recipient_candidate.split(':')
|
|
||||||
if model_name == 'event':
|
|
||||||
obj = event
|
|
||||||
elif model_name == 'plan':
|
|
||||||
obj = plan
|
|
||||||
else:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
return obj.data.get(key)
|
|
||||||
|
|
||||||
elif ':' in self.recipient_uuid_field and self.recipient_uuid_field.startswith('QS:'):
|
|
||||||
params, recipient_candidate = self.recipient_uuid_field.split('->')
|
|
||||||
query_set_flag, model, filters, = params.split(':')
|
|
||||||
filters_key_values = filters.split('&')
|
|
||||||
filter_params = {}
|
|
||||||
for key_value in filters_key_values:
|
|
||||||
key, value = key_value.split('=')
|
|
||||||
filter_params[key] = value
|
|
||||||
if model.lower() == 'event':
|
|
||||||
model_class = Event
|
|
||||||
elif model.lower() == 'plan':
|
|
||||||
model_class = Plan
|
|
||||||
elif model.lower() == 'promotion':
|
|
||||||
model_class = Promotion
|
|
||||||
else:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
model_object = model_class.objects.filter(**filter_params).last()
|
|
||||||
|
|
||||||
if model_object:
|
|
||||||
try:
|
|
||||||
uuid.UUID(recipient_candidate)
|
|
||||||
return recipient_candidate
|
|
||||||
except:
|
|
||||||
return model_object.data.get(recipient_candidate)
|
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
uuid.UUID(self.recipient_uuid_field)
|
|
||||||
return self.recipient_uuid_field
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_promotion_amount(self, plan=None, event=None):
|
|
||||||
# TODO: get base amount from policy_choice
|
|
||||||
base_amount = 0
|
|
||||||
max_promotion_amount = int(self.data.get('max_promotion_amount', 0))
|
|
||||||
promotion_percentage = int(self.data.get('promotion_percentage', 100))
|
|
||||||
try:
|
|
||||||
model_name, key = self.base_amount_field.split(':')
|
|
||||||
if model_name == 'event':
|
|
||||||
obj = event
|
|
||||||
elif model_name == 'plan':
|
|
||||||
obj = plan
|
|
||||||
else:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
base_amount = int(obj.data.get(key))
|
|
||||||
except Exception as e:
|
|
||||||
try:
|
|
||||||
base_amount = int(self.base_amount_field) # todo: to float
|
|
||||||
except:
|
|
||||||
logger.exception('can not pars base amount')
|
|
||||||
raise Exception('can not pars base amount')
|
|
||||||
|
|
||||||
promotion_amount = int(base_amount * (promotion_percentage / 100))
|
|
||||||
|
|
||||||
if max_promotion_amount:
|
|
||||||
if promotion_amount > max_promotion_amount:
|
|
||||||
return max_promotion_amount
|
|
||||||
else:
|
|
||||||
return promotion_amount
|
|
||||||
|
|
||||||
else:
|
|
||||||
return base_amount, promotion_amount
|
|
||||||
|
|
||||||
# @property
|
|
||||||
# def handler(self) -> BasePromotionHandler:
|
|
||||||
# return PromotionTypeChoices(self.promotion_type).handler(self)
|
|
||||||
|
|
||||||
def promote(self, plan=None, event=None):
|
|
||||||
recipient = self.get_recipient_uuid(plan, event)
|
|
||||||
if not recipient:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if not self.is_user_allowed(recipient):
|
|
||||||
return None
|
|
||||||
|
|
||||||
base_amount, promotion_amount = self.get_promotion_amount(plan=plan, event=event)
|
|
||||||
promotion, created = Promotion.objects.get_or_create(
|
|
||||||
promotion_amount=promotion_amount,
|
|
||||||
user_uuid=recipient,
|
|
||||||
event=event,
|
|
||||||
plan=plan,
|
|
||||||
recipient=self,
|
|
||||||
base_amount=base_amount,
|
|
||||||
)
|
|
||||||
if recipient and promotion_amount and created:
|
|
||||||
def _pending_rollback():
|
|
||||||
return PromotionTransaction.objects.filter(
|
|
||||||
user_uuid=recipient, event_label=event.label,
|
|
||||||
transaction_type=PromotionTransaction.TypeChoices.ROLLBACK,
|
|
||||||
).exclude(state=PaymentStateChoices.SUCCESS).first()
|
|
||||||
|
|
||||||
pending_rollback = _pending_rollback()
|
|
||||||
if pending_rollback is not None:
|
|
||||||
# The advertising service already asked to reverse this promotion
|
|
||||||
# before it was even paid -- don't move the money at all.
|
|
||||||
promotion.change_state(from_states=[promotion.state],
|
|
||||||
to_state=PaymentStateChoices.SUCCESS, same_ok=True)
|
|
||||||
PromotionTransaction.objects.filter(pk=pending_rollback.pk).update(
|
|
||||||
promotion=promotion, amount=0, state=PaymentStateChoices.SUCCESS,
|
|
||||||
updated_at=timezone.now())
|
|
||||||
promotion.refresh_from_db()
|
|
||||||
return promotion
|
|
||||||
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
reserved = self.plan.reserve_promotion_amount(promotion_amount)
|
|
||||||
if reserved:
|
|
||||||
promotion.promote()
|
|
||||||
else:
|
|
||||||
raise Exception('promotion reward not reserved')
|
|
||||||
except Exception as e:
|
|
||||||
promotion.change_state(from_states=[promotion.state], to_state=PaymentStateChoices.EXPECTED_FAILURE,
|
|
||||||
same_ok=True)
|
|
||||||
promotion.refresh_from_db()
|
|
||||||
|
|
||||||
# A rollback request may have arrived while the payout above was in
|
|
||||||
# flight (so the pre-check missed it). Reverse it now that it has settled.
|
|
||||||
if _pending_rollback() is not None:
|
|
||||||
rollback_promotion_payout(recipient, event.label)
|
|
||||||
|
|
||||||
return promotion
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class AllowedUser(BaseModel):
|
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='allowed_recipients')
|
|
||||||
recipient = models.ForeignKey(Recipient, on_delete=models.CASCADE, related_name='allowed_users')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(fields=['user', 'recipient'], name='unique_allowed_user_recipient')
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.user} -> {self.recipient}"
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentStateChoices(models.IntegerChoices):
|
|
||||||
CREATED = 1, _('created')
|
|
||||||
DELAYED = 2, _('delayed') # delayed as user wish
|
|
||||||
PENDING = 3, _('pending') # wait for external service response (or action)
|
|
||||||
INCOMPLETE = 4, _('incomplete') # started and wait for internal progress to complete
|
|
||||||
SUCCESS = 5, _('success')
|
|
||||||
FAILED = 6, _('failed')
|
|
||||||
EXPECTED_FAILURE = 7, _('expected_failure') # no exact data available but guessed to be failed
|
|
||||||
|
|
||||||
|
|
||||||
class PromotionQuerySet(models.QuerySet):
|
class PromotionQuerySet(models.QuerySet):
|
||||||
def used(self, user):
|
def used(self, user):
|
||||||
return self.filter(user=user, state__in=[Promotion.PaymentStateChoices.CREATED])
|
return self.filter(user=user, state__in=[Promotion.StateChoices.CREATED])
|
||||||
|
|
||||||
def is_processed_for_user(self, user_uuid, event_label):
|
|
||||||
return self.filter(user_uuid=user_uuid, event__label=event_label).exists()
|
|
||||||
|
|
||||||
|
|
||||||
def get_event_status_for_user(user_uuid, event_label):
|
|
||||||
processed = Promotion.objects.is_processed_for_user(user_uuid, event_label)
|
|
||||||
promotion_amount = 0
|
|
||||||
if not processed:
|
|
||||||
plan = Plan.objects.filter(event_list__icontains=event_label).first()
|
|
||||||
if plan:
|
|
||||||
promotion_amount = plan.get_configured_promotion_amount(user_uuid=user_uuid)
|
|
||||||
return {
|
|
||||||
'event_label': event_label,
|
|
||||||
'processed': processed,
|
|
||||||
'promotion_amount': promotion_amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Promotion(BaseModel):
|
class Promotion(BaseModel):
|
||||||
user_uuid = models.UUIDField() # user_uuid
|
class StateChoices(models.IntegerChoices):
|
||||||
|
CREATED = 0, _('Created')
|
||||||
|
ASSIGNED = 1, _('Assigned')
|
||||||
|
PROMOTED = 2, _('Promoted')
|
||||||
|
EXPIRED = 10, _('Expired')
|
||||||
|
|
||||||
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT,
|
||||||
related_name='+', null=True, blank=True)
|
related_name='+', null=True, blank=True)
|
||||||
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
plan = models.ForeignKey(Plan, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
||||||
event = models.ForeignKey(Event, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
state = models.IntegerField(choices=StateChoices.choices, default=StateChoices.CREATED)
|
||||||
recipient = models.ForeignKey(Recipient, on_delete=models.PROTECT, related_name='promotions', null=True, blank=True)
|
|
||||||
state = models.IntegerField(choices=PaymentStateChoices.choices, default=PaymentStateChoices.CREATED)
|
|
||||||
base_amount = models.IntegerField(null=True, blank=True)
|
base_amount = models.IntegerField(null=True, blank=True)
|
||||||
promotion_amount = models.IntegerField(null=True, blank=True)
|
promotion_amount = models.IntegerField(null=True, blank=True)
|
||||||
data = models.JSONField(null=True, blank=True)
|
data = models.JSONField(null=True, blank=True)
|
||||||
|
|
||||||
objects = PromotionQuerySet.as_manager()
|
objects = PromotionQuerySet.as_manager()
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
GinIndex(
|
|
||||||
fields=['data'],
|
|
||||||
# hashlib.md5("promotions_promotion_data_GinIndex".encode()).hexdigest()[:8] used for hash
|
|
||||||
name='promotions_promotion_cb76da3e'
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def change_state(self, from_states: list | tuple, to_state, same_ok=True, raise_exception=False,
|
def change_state(self, from_states: list | tuple, to_state, same_ok=True, raise_exception=False,
|
||||||
error_message=None,
|
error_message=None,
|
||||||
data: None | dict = None):
|
data: None | dict = None):
|
||||||
|
|
@ -493,284 +152,11 @@ class Promotion(BaseModel):
|
||||||
raise Exception(error_message)
|
raise Exception(error_message)
|
||||||
|
|
||||||
def promote(self, **kwargs):
|
def promote(self, **kwargs):
|
||||||
plan_data = kwargs.get('plan_data', {})
|
self.change_state(from_states=[Promotion.StateChoices.CREATED], to_state=Promotion.StateChoices.ASSIGNED,
|
||||||
plan_data['updated_at'] = timezone.now()
|
same_ok=False, raise_exception=True)
|
||||||
# plan_data = {"balance": 0}
|
|
||||||
|
|
||||||
logger.debug(f"start promotions: {self}")
|
handler = self.plan.handler
|
||||||
|
if handler.promote(promotion=self, **kwargs):
|
||||||
if self.state not in [PaymentStateChoices.CREATED]:
|
self.change_state(from_states=[Promotion.StateChoices.ASSIGNED], to_state=Promotion.StateChoices.PROMOTED)
|
||||||
raise Exception(_('Cannot promote. not in correct state'))
|
|
||||||
|
|
||||||
if not self.promotion_amount:
|
|
||||||
to_not_payed = self.change_state(
|
|
||||||
from_states=[PaymentStateChoices.CREATED],
|
|
||||||
to_state=PaymentStateChoices.SUCCESS,
|
|
||||||
same_ok=False,
|
|
||||||
raise_exception=True,
|
|
||||||
error_message='Failed to update state',
|
|
||||||
)
|
|
||||||
return bool(to_not_payed)
|
|
||||||
|
|
||||||
to_pending = self.change_state(
|
|
||||||
from_states=[self.state],
|
|
||||||
to_state=PaymentStateChoices.PENDING,
|
|
||||||
same_ok=False,
|
|
||||||
raise_exception=True,
|
|
||||||
error_message='Cannot promote. not in current state'
|
|
||||||
)
|
|
||||||
|
|
||||||
# The wallet transfer itself is recorded and driven by a PromotionTransaction
|
|
||||||
# ledger row (mirrors advertising's AdPayment / escrow's EscrowWalletPayment),
|
|
||||||
# so every promotion payout has an auditable record and can be reversed.
|
|
||||||
payout, _created = PromotionTransaction.objects.get_or_create(
|
|
||||||
promotion=self,
|
|
||||||
transaction_type=PromotionTransaction.TypeChoices.PAYOUT,
|
|
||||||
defaults=dict(
|
|
||||||
user_uuid=self.user_uuid,
|
|
||||||
event_label=self.event.label if self.event_id else '',
|
|
||||||
holder_wallet=settings.WALLET_PROMOTIONS_CREDIT,
|
|
||||||
destination_wallet=self.recipient.get_wallet_category_uuid(),
|
|
||||||
amount=self.promotion_amount,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
transferred = payout.execute()
|
|
||||||
except Exception:
|
|
||||||
self.change_state(
|
|
||||||
from_states=[self.state],
|
|
||||||
to_state=PaymentStateChoices.EXPECTED_FAILURE,
|
|
||||||
same_ok=True,
|
|
||||||
raise_exception=True,
|
|
||||||
error_message='Failed to update state',
|
|
||||||
)
|
|
||||||
raise Exception('Failed to pay promotion')
|
|
||||||
|
|
||||||
if not transferred:
|
|
||||||
self.change_state(
|
|
||||||
from_states=[self.state],
|
|
||||||
to_state=PaymentStateChoices.FAILED,
|
|
||||||
same_ok=True,
|
|
||||||
raise_exception=True,
|
|
||||||
error_message='Failed to update state payment',
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
notifications_push_user(self.user_uuid, self.recipient.label, self.recipient.label, 5, extras={})
|
|
||||||
except:
|
|
||||||
# TODO: log
|
|
||||||
pass
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
# updated = Plan.objects.filter(pk=self.plan.pk).update(balance=F('balance') - self.promotion_amount, **plan_data)
|
|
||||||
updated = Plan.objects.filter(pk=self.plan.pk).update(**plan_data)
|
|
||||||
if updated:
|
|
||||||
to_pay_success = self.change_state(
|
|
||||||
from_states=[self.state],
|
|
||||||
to_state=PaymentStateChoices.SUCCESS,
|
|
||||||
same_ok=False,
|
|
||||||
raise_exception=True,
|
|
||||||
error_message='Failed to update state refund',
|
|
||||||
)
|
|
||||||
return bool(to_pay_success)
|
|
||||||
else:
|
|
||||||
# TODO: is it possible?
|
|
||||||
to_pay_failed = self.change_state(
|
|
||||||
from_states=[self.state],
|
|
||||||
to_state=PaymentStateChoices.EXPECTED_FAILURE,
|
|
||||||
same_ok=False,
|
|
||||||
raise_exception=True,
|
|
||||||
error_message='Failed to update state payment',
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_rollbackable(self):
|
|
||||||
"""A promotion payout can only be reversed if it actually landed in the
|
|
||||||
advertising transit wallet. A payout made straight to the user's income
|
|
||||||
wallet is the user's money and is never clawed back."""
|
|
||||||
return bool(
|
|
||||||
self.recipient_id
|
|
||||||
and self.recipient.wallet_destination == WalletDestinationChoices.ADVERTISING_TRANSIT
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_rolled_back(self):
|
|
||||||
return self.transactions.filter(
|
|
||||||
transaction_type=PromotionTransaction.TypeChoices.ROLLBACK,
|
|
||||||
state=PaymentStateChoices.SUCCESS,
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
|
|
||||||
IN_FLIGHT_PAYMENT_STATES = (
|
|
||||||
PaymentStateChoices.CREATED,
|
|
||||||
PaymentStateChoices.PENDING,
|
|
||||||
PaymentStateChoices.DELAYED,
|
|
||||||
PaymentStateChoices.INCOMPLETE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PromotionTransaction(BaseModel):
|
|
||||||
"""Ledger of every wallet transfer the promotions service makes -- the
|
|
||||||
payout for a promotion, and any later reversal of it. Mirrors advertising's
|
|
||||||
``AdPayment`` and escrow's ``EscrowWalletPayment``: each row drives one
|
|
||||||
external wallet call through its own CREATED -> PENDING -> SUCCESS/FAILED
|
|
||||||
state, so the money movement is auditable and reversible.
|
|
||||||
|
|
||||||
``user_uuid`` / ``event_label`` are denormalised onto the row so a ROLLBACK
|
|
||||||
can be recorded before the async payout task has even created its
|
|
||||||
``Promotion`` (see :func:`rollback_promotion_payout` and
|
|
||||||
``Recipient.promote``).
|
|
||||||
"""
|
|
||||||
|
|
||||||
class TypeChoices(models.IntegerChoices):
|
|
||||||
PAYOUT = 1, _('payout') # promotions credit -> recipient wallet (transit or user)
|
|
||||||
ROLLBACK = 2, _('rollback') # advertising transit -> promotions credit
|
|
||||||
|
|
||||||
promotion = models.ForeignKey(Promotion, on_delete=models.SET_NULL, null=True, blank=True,
|
|
||||||
related_name='transactions')
|
|
||||||
user_uuid = models.UUIDField(db_index=True)
|
|
||||||
event_label = models.CharField(max_length=255, db_index=True)
|
|
||||||
transaction_type = models.IntegerField(choices=TypeChoices.choices, db_index=True)
|
|
||||||
holder_wallet = models.UUIDField() # 1st arg to deposit_to_user_wallet_submit
|
|
||||||
destination_wallet = models.UUIDField() # data['payee_wallet']
|
|
||||||
amount = models.IntegerField(default=0)
|
|
||||||
state = models.IntegerField(choices=PaymentStateChoices.choices,
|
|
||||||
default=PaymentStateChoices.CREATED, db_index=True)
|
|
||||||
external_uuid = models.UUIDField(default=uuid.uuid4, editable=False) # idempotency key to the wallet service
|
|
||||||
reverses = models.ForeignKey('self', on_delete=models.PROTECT, null=True, blank=True,
|
|
||||||
related_name='reversed_by')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['created_at']
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(fields=['promotion', 'transaction_type'],
|
|
||||||
name='unique_promotion_transaction_type'),
|
|
||||||
models.UniqueConstraint(fields=['user_uuid', 'event_label'],
|
|
||||||
condition=models.Q(transaction_type=2),
|
|
||||||
name='unique_promotion_rollback'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.get_transaction_type_display()} {self.amount} ({self.event_label})"
|
|
||||||
|
|
||||||
def _set_state(self, state):
|
|
||||||
PromotionTransaction.objects.filter(pk=self.pk).update(state=state, updated_at=timezone.now())
|
|
||||||
self.state = state
|
|
||||||
|
|
||||||
def execute(self):
|
|
||||||
"""Run the wallet transfer once. Returns True only on a confirmed
|
|
||||||
transfer; leaves the row FAILED / EXPECTED_FAILURE (and re-raises) on
|
|
||||||
error, same as the promote()/AdPayment style. No automatic retry."""
|
|
||||||
if self.state == PaymentStateChoices.SUCCESS:
|
|
||||||
return True
|
return True
|
||||||
if self.state != PaymentStateChoices.CREATED:
|
return False
|
||||||
return False
|
|
||||||
if not self.amount:
|
|
||||||
self._set_state(PaymentStateChoices.SUCCESS)
|
|
||||||
return True
|
|
||||||
|
|
||||||
self._set_state(PaymentStateChoices.PENDING)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"uuid": str(self.external_uuid),
|
|
||||||
"payee": str(self.user_uuid),
|
|
||||||
"payee_type": 1,
|
|
||||||
"payee_wallet": str(self.destination_wallet),
|
|
||||||
"amount": self.amount,
|
|
||||||
"details": {
|
|
||||||
'description': str(self.get_transaction_type_display()),
|
|
||||||
'reference_id': str(self.pk),
|
|
||||||
'application_details_url': '',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
submit_response = deposit_to_user_wallet_submit(str(self.holder_wallet), data)
|
|
||||||
if not getattr(submit_response, 'uuid', None):
|
|
||||||
raise Exception('wallet submit returned no uuid')
|
|
||||||
except Exception:
|
|
||||||
self._set_state(PaymentStateChoices.FAILED)
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
verify_response = deposit_to_user_wallet_verify(str(self.holder_wallet), submit_response.uuid)
|
|
||||||
if not getattr(verify_response, 'uuid', None):
|
|
||||||
raise Exception('wallet verify returned no uuid')
|
|
||||||
except Exception:
|
|
||||||
self._set_state(PaymentStateChoices.EXPECTED_FAILURE)
|
|
||||||
raise
|
|
||||||
|
|
||||||
if verify_response.state != 5:
|
|
||||||
self._set_state(PaymentStateChoices.FAILED)
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._set_state(PaymentStateChoices.SUCCESS)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def rollback_promotion_payout(user_uuid, event_label):
|
|
||||||
"""Reverse a promotion payout for ``(user_uuid, event_label)`` back out of
|
|
||||||
the advertising transit wallet into the promotions credit wallet.
|
|
||||||
|
|
||||||
The promotion stays consumed -- the user cannot earn it again -- only the
|
|
||||||
money moves, and only if the payout actually landed in the transit wallet.
|
|
||||||
Idempotent. Returns ``(status, amount)`` where status is one of
|
|
||||||
``'reversed'`` (money moved), ``'deferred'`` (payout not processed yet --
|
|
||||||
``Recipient.promote()`` will suppress it), ``'nothing'`` (nothing to
|
|
||||||
reverse: zero payout, or paid straight to the user's wallet).
|
|
||||||
"""
|
|
||||||
with transaction.atomic():
|
|
||||||
existing = PromotionTransaction.objects.select_for_update().filter(
|
|
||||||
user_uuid=user_uuid, event_label=event_label,
|
|
||||||
transaction_type=PromotionTransaction.TypeChoices.ROLLBACK,
|
|
||||||
).first()
|
|
||||||
if existing is not None and existing.state == PaymentStateChoices.SUCCESS:
|
|
||||||
return ('reversed' if existing.amount else 'nothing', existing.amount)
|
|
||||||
|
|
||||||
payout = PromotionTransaction.objects.select_for_update().filter(
|
|
||||||
user_uuid=user_uuid, event_label=event_label,
|
|
||||||
transaction_type=PromotionTransaction.TypeChoices.PAYOUT,
|
|
||||||
).first()
|
|
||||||
|
|
||||||
payout_in_flight = payout is not None and payout.state in IN_FLIGHT_PAYMENT_STATES
|
|
||||||
promotion_settled_without_payout = payout is None and Promotion.objects.filter(
|
|
||||||
user_uuid=user_uuid, event__label=event_label, state=PaymentStateChoices.SUCCESS,
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
if payout is not None and payout.state == PaymentStateChoices.SUCCESS \
|
|
||||||
and payout.promotion_id and payout.promotion.is_rollbackable() and payout.amount:
|
|
||||||
amount = payout.amount
|
|
||||||
else:
|
|
||||||
amount = 0
|
|
||||||
|
|
||||||
promo = payout.promotion if payout is not None else None
|
|
||||||
|
|
||||||
rollback, created = PromotionTransaction.objects.get_or_create(
|
|
||||||
user_uuid=user_uuid, event_label=event_label,
|
|
||||||
transaction_type=PromotionTransaction.TypeChoices.ROLLBACK,
|
|
||||||
defaults=dict(
|
|
||||||
promotion=promo,
|
|
||||||
holder_wallet=settings.WALLET_ADVERTISING_TRANSIT,
|
|
||||||
destination_wallet=settings.WALLET_PROMOTIONS_CREDIT,
|
|
||||||
amount=amount,
|
|
||||||
reverses=payout,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if not created and rollback.state != PaymentStateChoices.SUCCESS:
|
|
||||||
# refresh the amount/links now that the payout may have appeared
|
|
||||||
PromotionTransaction.objects.filter(pk=rollback.pk).exclude(
|
|
||||||
state=PaymentStateChoices.SUCCESS).update(
|
|
||||||
promotion=promo, amount=amount, reverses=payout, updated_at=timezone.now())
|
|
||||||
rollback.amount = amount
|
|
||||||
|
|
||||||
if payout is None and not promotion_settled_without_payout:
|
|
||||||
return ('deferred', 0) # honoured later by Recipient.promote()
|
|
||||||
if payout_in_flight:
|
|
||||||
return ('deferred', 0)
|
|
||||||
if amount == 0:
|
|
||||||
rollback._set_state(PaymentStateChoices.SUCCESS)
|
|
||||||
return ('nothing', 0)
|
|
||||||
|
|
||||||
rollback.execute() # raises on wallet failure -> caller aborts + retries
|
|
||||||
return ('reversed', amount)
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,22 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import Promotion, Plan, Event, Recipient
|
from .models import Promotion, Plan, Event
|
||||||
|
|
||||||
|
|
||||||
|
class PromotionSerializer(serializers.ModelSerializer):
|
||||||
|
base_amount = serializers.IntegerField(required=True)
|
||||||
|
promotion_amount = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Promotion
|
||||||
|
fields = (
|
||||||
|
"base_amount",
|
||||||
|
"promotion_amount",
|
||||||
|
"plan",
|
||||||
|
"user",
|
||||||
|
"application",
|
||||||
|
)
|
||||||
|
read_only_fields = ('user','application', "promotion_amount",)
|
||||||
|
|
||||||
|
|
||||||
class EventSerializer(serializers.ModelSerializer):
|
class EventSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -12,25 +28,7 @@ class EventSerializer(serializers.ModelSerializer):
|
||||||
"user",
|
"user",
|
||||||
"application",
|
"application",
|
||||||
)
|
)
|
||||||
read_only_fields = ('user', 'application')
|
read_only_fields = ('user','application')
|
||||||
|
|
||||||
|
|
||||||
class PromotionSerializer(serializers.ModelSerializer):
|
|
||||||
# base_amount = serializers.IntegerField(required=True)
|
|
||||||
# promotion_amount = serializers.IntegerField(read_only=True)
|
|
||||||
event = EventSerializer(read_only=True)
|
|
||||||
user = serializers.UUIDField(source='user_uuid')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Promotion
|
|
||||||
fields = (
|
|
||||||
"uuid",
|
|
||||||
"event",
|
|
||||||
"promotion_amount",
|
|
||||||
"state",
|
|
||||||
"user",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PlanSerializer(serializers.ModelSerializer):
|
class PlanSerializer(serializers.ModelSerializer):
|
||||||
base_amount = serializers.IntegerField(required=True)
|
base_amount = serializers.IntegerField(required=True)
|
||||||
|
|
@ -38,12 +36,13 @@ class PlanSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Plan
|
model = Plan
|
||||||
fields = ("title",
|
fields = (
|
||||||
"description",
|
"title",
|
||||||
"base_amount",
|
"description",
|
||||||
"application",
|
"base_amount",
|
||||||
"promotion_amount"
|
"application",
|
||||||
)
|
"promotion_amount"
|
||||||
|
)
|
||||||
read_only_fields = ('application', "promotion_amount", "title", "description")
|
read_only_fields = ('application', "promotion_amount", "title", "description")
|
||||||
|
|
||||||
# def get_promotion_amount(self, obj):
|
# def get_promotion_amount(self, obj):
|
||||||
|
|
@ -58,59 +57,5 @@ class PlanPromotSerializer(serializers.ModelSerializer):
|
||||||
"user",
|
"user",
|
||||||
"application",
|
"application",
|
||||||
)
|
)
|
||||||
read_only_fields = ('user', 'application')
|
read_only_fields = ('user','application')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PromoteSerializer(serializers.ModelSerializer):
|
|
||||||
label = serializers.CharField(write_only=True)
|
|
||||||
data = serializers.JSONField(write_only=True)
|
|
||||||
|
|
||||||
promotions = PromotionSerializer(many=True, read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Promotion
|
|
||||||
fields = (
|
|
||||||
"promotions",
|
|
||||||
"label",
|
|
||||||
"data",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserRecipientSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Recipient
|
|
||||||
fields = ['label',
|
|
||||||
"base_amount_field"]
|
|
||||||
|
|
||||||
|
|
||||||
class PromotionStatusSerializer(serializers.Serializer):
|
|
||||||
event_label = serializers.CharField(read_only=True)
|
|
||||||
processed = serializers.BooleanField(read_only=True)
|
|
||||||
promotion_amount = serializers.IntegerField(read_only=True, allow_null=True)
|
|
||||||
|
|
||||||
|
|
||||||
class PromotionRollbackSerializer(serializers.Serializer):
|
|
||||||
event_label = serializers.CharField(read_only=True)
|
|
||||||
# reversed -> money moved back out of the advertising transit wallet
|
|
||||||
# deferred -> payout not settled yet; it will be suppressed when it runs
|
|
||||||
# nothing -> nothing to reverse (already rolled back, or paid to the user)
|
|
||||||
status = serializers.ChoiceField(read_only=True, choices=['reversed', 'deferred', 'nothing'])
|
|
||||||
amount = serializers.IntegerField(read_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
class UserPlanSerializer(serializers.ModelSerializer):
|
|
||||||
recipients = UserRecipientSerializer(many=True, read_only=True)
|
|
||||||
class Meta:
|
|
||||||
model = Plan
|
|
||||||
fields = ("title",
|
|
||||||
'banner',
|
|
||||||
"description",
|
|
||||||
"description_details",
|
|
||||||
"recipients",
|
|
||||||
)
|
|
||||||
read_only_fields = ("title", "description", "description_details", 'banner', "recipients")
|
|
||||||
|
|
||||||
# def get_promotion_amount(self, obj):
|
|
||||||
# user = self.context['request'].user
|
|
||||||
# return obj.calculate_promotion(user)
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from apps.promotions.models import Event
|
from apps.promotions.models import Event
|
||||||
from main import celery_app
|
from main import celery_app
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task
|
@celery_app.task
|
||||||
def analyze_event_task(event_uuid):
|
def analyze_event_task(event_uuid):
|
||||||
event = Event.objects.get(uuid=event_uuid)
|
event = Event.objects.get(uuid=event_uuid)
|
||||||
for plan_analyze_result in event.analyze():
|
event.analyze()
|
||||||
try:
|
|
||||||
for promotion in plan_analyze_result:
|
|
||||||
print(promotion)
|
|
||||||
except Exception:
|
|
||||||
logger.exception('Failed to process plan for event %s', event_uuid)
|
|
||||||
|
|
|
||||||
|
|
@ -1,900 +1,3 @@
|
||||||
import uuid
|
from django.test import TestCase
|
||||||
from datetime import timedelta
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from django.urls import reverse
|
# Create your tests here.
|
||||||
from django.utils import timezone
|
|
||||||
from oauth2_provider.models import get_access_token_model, get_application_model
|
|
||||||
from rest_framework.test import APITestCase, override_settings, APIClient
|
|
||||||
|
|
||||||
from apps.promotions.tasks import analyze_event_task
|
|
||||||
from apps.users.models import User
|
|
||||||
from apps.promotions.models import Plan, Promotion, EventSaver, ProcessorTypeChoices, Event, Recipient, \
|
|
||||||
PaymentStateChoices, WalletDestinationChoices
|
|
||||||
|
|
||||||
AccessToken = get_access_token_model()
|
|
||||||
Application = get_application_model()
|
|
||||||
|
|
||||||
application_uuid_hex = 'b14e8b86-8f4a-44d9-b29d-badceb470053'
|
|
||||||
|
|
||||||
|
|
||||||
def mock_submit_deposit_success(wallet_uuid, data):
|
|
||||||
import uuid as sys_uuid
|
|
||||||
class Tmp():
|
|
||||||
uuid = sys_uuid.uuid4()
|
|
||||||
|
|
||||||
data = Tmp()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def mock_verify_deposit_success(wallet_uuid, transaction_uuid):
|
|
||||||
import uuid as sys_uuid
|
|
||||||
class Tmp():
|
|
||||||
uuid = sys_uuid.uuid4()
|
|
||||||
state = 5
|
|
||||||
|
|
||||||
data = Tmp()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def mock_submit_withdraw_success(data):
|
|
||||||
import uuid as sys_uuid
|
|
||||||
class Tmp():
|
|
||||||
uuid = sys_uuid.uuid4()
|
|
||||||
|
|
||||||
data = Tmp()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def mock_verify_withdraw_success(uuid):
|
|
||||||
import uuid as sys_uuid
|
|
||||||
class Tmp():
|
|
||||||
uuid = sys_uuid.uuid4()
|
|
||||||
state = 5
|
|
||||||
|
|
||||||
data = Tmp()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationApiFlowsTests(APITestCase):
|
|
||||||
client_id = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
|
|
||||||
application_uuid = uuid.UUID(application_uuid_hex)
|
|
||||||
application_access_token = '14naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
|
||||||
user_access_token = '24naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
|
||||||
user_1_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005f')
|
|
||||||
user_2_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb470052')
|
|
||||||
first_ad_view_reward_label = 'first_ad_view_reward_label'
|
|
||||||
referral_reward_label = 'referral_reward_label'
|
|
||||||
settlement_reward_label = 'settlement_reward_label'
|
|
||||||
serial_reward_label = 'serial_reward_label'
|
|
||||||
|
|
||||||
scopes = [
|
|
||||||
'promotions.application.event:submit',
|
|
||||||
'promotions.user.self-plans:list-retrieve',
|
|
||||||
'promotions.application.user-promotions:promote',
|
|
||||||
'promotions.application.user-promotions:list-retrieve',
|
|
||||||
'promotions.application.user-plan:promote',
|
|
||||||
'promotions.application.user-plan:list-retrieve'
|
|
||||||
]
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.submit_deposit_success_patcher = patch('apps.promotions.models.deposit_to_user_wallet_submit',
|
|
||||||
mock_submit_deposit_success)
|
|
||||||
self.verify_deposit_success_patcher = patch('apps.promotions.models.deposit_to_user_wallet_verify',
|
|
||||||
mock_verify_deposit_success)
|
|
||||||
# self.submit_withdraw_success_patcher = patch('apps.promotions.models.withdraw_from_user_wallet_submit',
|
|
||||||
# mock_submit_withdraw_success)
|
|
||||||
# self.verify_withdraw_success_patcher = patch('apps.promotions.models.withdraw_from_user_wallet_verify',
|
|
||||||
# mock_verify_withdraw_success)
|
|
||||||
|
|
||||||
self.submit_deposit_success_patcher.start()
|
|
||||||
self.verify_deposit_success_patcher.start()
|
|
||||||
# self.submit_withdraw_success_patcher.start()
|
|
||||||
# self.verify_withdraw_success_patcher.start()
|
|
||||||
|
|
||||||
self.user_1, _ = User.objects.get_or_create(pk=self.user_1_uuid)
|
|
||||||
self.user_2, _ = User.objects.get_or_create(pk=self.user_2_uuid)
|
|
||||||
|
|
||||||
expire_datetime = timezone.now() + timedelta(seconds=3600)
|
|
||||||
expire_datetime.isoformat()
|
|
||||||
|
|
||||||
self.application_access_token, _created = AccessToken.objects.update_or_create(
|
|
||||||
token=self.application_access_token,
|
|
||||||
defaults={
|
|
||||||
"client_id": self.client_id,
|
|
||||||
# "client_owner": owner,
|
|
||||||
"application_id": self.application_uuid,
|
|
||||||
"scope": ' '.join(self.scopes),
|
|
||||||
"expires": expire_datetime.isoformat(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.user_access_token, _created = AccessToken.objects.update_or_create(
|
|
||||||
token=self.user_access_token,
|
|
||||||
defaults={
|
|
||||||
"user": self.user_1,
|
|
||||||
"client_id": self.client_id,
|
|
||||||
# "client_owner": owner,
|
|
||||||
"application_id": self.application_uuid,
|
|
||||||
"scope": ' '.join(self.scopes),
|
|
||||||
"expires": expire_datetime.isoformat(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.application, _created = Application.objects.get_or_create(
|
|
||||||
client_id=self.client_id,
|
|
||||||
uuid=self.application_uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
def _create_authorization_header(self, token):
|
|
||||||
return "Bearer {0}".format(token)
|
|
||||||
|
|
||||||
def test_first_ad_view_reward_success(self):
|
|
||||||
self.first_ad_view_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
event_label=self.first_ad_view_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.first_ad_view_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
balance=10000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.first_ad_view_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.first_ad_view_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label=self.first_ad_view_reward_label,
|
|
||||||
plan=self.first_ad_view_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field="->event:user",
|
|
||||||
base_amount_field="event:base_amount",
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.first_ad_view_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'base_amount': 800
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('promotions:promotion-list', kwargs={"plan": self.first_ad_view_reward_plan.uuid}),
|
|
||||||
event_create_data, HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json')
|
|
||||||
print(response.json())
|
|
||||||
print(response.status_code)
|
|
||||||
# response = self.client.post(reverse('promotions:user-promote', kwargs={"pk":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={"plan": self.first_ad_view_reward_plan.uuid}),
|
|
||||||
event_create_data, HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json')
|
|
||||||
print(response.json())
|
|
||||||
self.first_ad_view_reward_plan.refresh_from_db()
|
|
||||||
self.assertEqual(self.first_ad_view_reward_plan.balance, 10000 - 800)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('promotions:promotion-list', kwargs={"plan": self.first_ad_view_reward_plan.uuid}),
|
|
||||||
event_create_data, HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
# response = self.client.post(reverse('promotions:promotion-create', kwargs={"plan": self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
# print(response.json())
|
|
||||||
|
|
||||||
def test_referral_reward_success(self):
|
|
||||||
self.referral_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.referral_reward_label,
|
|
||||||
event_label=self.referral_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.referral_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.referral_reward_label,
|
|
||||||
balance=10000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.referral_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.referral_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label=self.referral_reward_label,
|
|
||||||
plan=self.referral_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field="->event:referral",
|
|
||||||
base_amount_field="1000",
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.referral_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'referral': self.user_2_uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={"plan": self.referral_reward_plan.uuid}), event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json')
|
|
||||||
print(response.json())
|
|
||||||
self.referral_reward_plan.refresh_from_db()
|
|
||||||
self.assertEqual(self.referral_reward_plan.balance, 10000 - 1000)
|
|
||||||
self.assertEqual(response.json()['promotions'][0]['user'], str(self.user_2_uuid))
|
|
||||||
|
|
||||||
def test_referral_reward_submit_settlement_success(self):
|
|
||||||
self.referral_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.referral_reward_label,
|
|
||||||
event_label=self.referral_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.settlement_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.settlement_reward_label,
|
|
||||||
event_label=self.settlement_reward_label,
|
|
||||||
save_once=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.settlement_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.settlement_reward_label,
|
|
||||||
balance=100000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.settlement_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.settlement_referral_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label='settlement_referral_reward_label',
|
|
||||||
plan=self.settlement_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
# recipient_uuid_field=self.user_2_uuid,
|
|
||||||
recipient_uuid_field=f"QS:Event:label={self.referral_reward_label}&data__referral={self.user_2_uuid}->{self.user_2_uuid}",
|
|
||||||
base_amount_field="event:base_amount",
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
submit_referral_create_data = {
|
|
||||||
'label': self.referral_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'referral': self.user_2_uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
submit_settlement_create_data = {
|
|
||||||
'label': self.settlement_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'settlement': self.user_2_uuid,
|
|
||||||
'base_amount': 7000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.assertEqual(Event.objects.count(), 0)
|
|
||||||
response = self.client.post(reverse('promotions:event-create'), submit_referral_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
self.settlement_reward_plan.refresh_from_db()
|
|
||||||
self.assertEqual(self.settlement_reward_plan.balance, 100000)
|
|
||||||
self.assertEqual(Event.objects.count(), 1)
|
|
||||||
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={'plan': str(self.settlement_reward_plan.uuid)}),
|
|
||||||
submit_settlement_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={'plan': str(self.settlement_reward_plan.uuid)}),
|
|
||||||
submit_settlement_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
def test_referral_percentage_reward_submit_settlement_success(self):
|
|
||||||
self.referral_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.referral_reward_label,
|
|
||||||
event_label=self.referral_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.settlement_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.settlement_reward_label,
|
|
||||||
event_label=self.settlement_reward_label,
|
|
||||||
save_once=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.settlement_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.settlement_reward_label,
|
|
||||||
balance=100000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.settlement_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.settlement_referral_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label='settlement_referral_reward_label',
|
|
||||||
plan=self.settlement_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
# recipient_uuid_field=self.user_2_uuid,
|
|
||||||
recipient_uuid_field=f"QS:Event:label={self.referral_reward_label}&data__referral={self.user_2_uuid}->{self.user_2_uuid}",
|
|
||||||
base_amount_field="event:base_amount",
|
|
||||||
data={"max_promotion_amount": 0, "promotion_percentage": 50}
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
submit_referral_create_data = {
|
|
||||||
'label': self.referral_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'referral': self.user_2_uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
submit_settlement_create_data = {
|
|
||||||
'label': self.settlement_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'settlement': self.user_2_uuid,
|
|
||||||
'base_amount': 7000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.assertEqual(Event.objects.count(), 0)
|
|
||||||
response = self.client.post(reverse('promotions:event-create'), submit_referral_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
self.settlement_reward_plan.refresh_from_db()
|
|
||||||
self.assertEqual(self.settlement_reward_plan.balance, 100000)
|
|
||||||
self.assertEqual(Event.objects.count(), 1)
|
|
||||||
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={'plan': str(self.settlement_reward_plan.uuid)}),
|
|
||||||
submit_settlement_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={'plan': str(self.settlement_reward_plan.uuid)}),
|
|
||||||
submit_settlement_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
def test_serial_ad_view_promotion_success(self):
|
|
||||||
serial_id = 'b14e8b86-8f4a-44d9-b29d-badceb470053'
|
|
||||||
ad_1 = 'b14e8b86-8f4a-44d9-b29d-badceb470054'
|
|
||||||
ad_2 = 'b14e8b86-8f4a-44d9-b29d-badceb470055'
|
|
||||||
ad_3 = 'b14e8b86-8f4a-44d9-b29d-badceb470056'
|
|
||||||
self.serial_ad_view_1_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title='serial_ad_view_1',
|
|
||||||
event_label='serial_ad_view_1',
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.serial_ad_view_2_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title='serial_ad_view_2',
|
|
||||||
event_label='serial_ad_view_2',
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.serial_ad_view_3_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title='serial_ad_view_3',
|
|
||||||
event_label='serial_ad_view_3',
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.serial_promotion_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.serial_reward_label,
|
|
||||||
balance=100000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=['serial_ad_view_1', 'serial_ad_view_2', 'serial_ad_view_3']
|
|
||||||
)
|
|
||||||
|
|
||||||
self.serial_ad_view_1_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label='serial_ad_view_1_event_saver_label',
|
|
||||||
plan=self.serial_promotion_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field=f"event:label=serial_ad_view_1&event:data__serial={serial_id}->event:user",
|
|
||||||
base_amount_field=300,
|
|
||||||
# data={"max_promotion_amount": 0, "promotion_percentage": 50}
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
self.serial_ad_view_2_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label='serial_ad_view_2_event_saver_label',
|
|
||||||
plan=self.serial_promotion_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field=f"event:label=serial_ad_view_2&event:data__serial={serial_id}->event:user",
|
|
||||||
# recipient_uuid_field=f"QS:Event:label=serial_ad_view_2&data__user={self.user_1_uuid}&data__serial={serial_id}->user",
|
|
||||||
base_amount_field=200,
|
|
||||||
# data={"max_promotion_amount": 0, "promotion_percentage": 50}
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
self.serial_ad_view_3_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label='serial_ad_view_3_event_saver_label',
|
|
||||||
plan=self.serial_promotion_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field=f"event:label=serial_ad_view_3&event:data__serial={serial_id}->event:user",
|
|
||||||
# recipient_uuid_field=f"QS:Event:label=serial_ad_view_3&data__user={self.user_1_uuid}&data__serial={serial_id}->user",
|
|
||||||
base_amount_field=100,
|
|
||||||
# data={"max_promotion_amount": 0, "promotion_percentage": 50}
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
|
|
||||||
serial_ad_1_event_create_data = {
|
|
||||||
'label': 'serial_ad_view_1',
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'serial': serial_id,
|
|
||||||
'ad': ad_1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serial_ad_2_event_create_data = {
|
|
||||||
'label': 'serial_ad_view_2',
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'serial': serial_id,
|
|
||||||
'ad': ad_2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serial_ad_3_event_create_data = {
|
|
||||||
'label': 'serial_ad_view_3',
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'serial': serial_id,
|
|
||||||
'ad': ad_3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.assertEqual(Event.objects.count(), 0)
|
|
||||||
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={"plan": str(self.serial_promotion_plan.pk)}),
|
|
||||||
serial_ad_1_event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={"plan": str(self.serial_promotion_plan.pk)}),
|
|
||||||
serial_ad_2_event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
|
|
||||||
# response = self.client.post(reverse('promotions:event-create'), serial_ad_2_event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
# event_2 = Event.objects.first()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
response = self.client.post(reverse('promotions:event-create'), serial_ad_1_event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
event_1 = Event.objects.first()
|
|
||||||
response = self.client.post(reverse('promotions:event-create'), serial_ad_2_event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
event_2 = Event.objects.first()
|
|
||||||
response = self.client.post(reverse('promotions:event-create'), serial_ad_3_event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
event_3 = Event.objects.first()
|
|
||||||
|
|
||||||
analyze_event_task(event_1.uuid)
|
|
||||||
analyze_event_task(event_2.uuid)
|
|
||||||
analyze_event_task(event_3.uuid)
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
self.serial_promotion_plan.refresh_from_db()
|
|
||||||
|
|
||||||
self.assertEqual(self.serial_promotion_plan.balance, 100000)
|
|
||||||
self.assertEqual(Event.objects.count(), 3)
|
|
||||||
|
|
||||||
def test_plan_balance_from_another_promotion_success(self):
|
|
||||||
base_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title='base_plan',
|
|
||||||
balance=0,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=['advertising:manual']
|
|
||||||
)
|
|
||||||
|
|
||||||
base_promotion, _created = Promotion.objects.get_or_create(
|
|
||||||
user_uuid=self.user_1_uuid,
|
|
||||||
application=self.application,
|
|
||||||
plan=base_plan,
|
|
||||||
state=PaymentStateChoices.SUCCESS,
|
|
||||||
base_amount=10000,
|
|
||||||
promotion_amount=10000
|
|
||||||
)
|
|
||||||
|
|
||||||
promotion_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
event_label=self.first_ad_view_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
promotion_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title='promotion_plan',
|
|
||||||
balance=0,
|
|
||||||
balance_holder=base_promotion.pk,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.first_ad_view_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.promotion_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label='promotion_recipient',
|
|
||||||
plan=promotion_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field=self.user_2_uuid,
|
|
||||||
base_amount_field=1000,
|
|
||||||
data={}
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
|
|
||||||
create_data = {
|
|
||||||
'label': self.first_ad_view_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.assertEqual(Event.objects.count(), 0)
|
|
||||||
response = self.client.post(reverse('promotions:promotion-create', kwargs={'plan': str(promotion_plan.uuid)}),
|
|
||||||
create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
promotion_plan.refresh_from_db()
|
|
||||||
base_promotion.refresh_from_db()
|
|
||||||
self.assertEqual(base_promotion.base_amount, 10000)
|
|
||||||
self.assertEqual(base_promotion.promotion_amount, 9000)
|
|
||||||
self.assertEqual(Event.objects.count(), 1)
|
|
||||||
|
|
||||||
response = self.client.post(reverse('promotions:promotion-create', kwargs={'plan': str(promotion_plan.uuid)}),
|
|
||||||
create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
def test_application_promote_user_success(self):
|
|
||||||
self.first_ad_view_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
event_label=self.first_ad_view_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.first_ad_view_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
balance=10000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.first_ad_view_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.first_ad_view_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label=self.first_ad_view_reward_label,
|
|
||||||
plan=self.first_ad_view_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field="->event:user",
|
|
||||||
base_amount_field="event:base_amount",
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.first_ad_view_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'base_amount': 800
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url = reverse('promotions-application:plan-promote',
|
|
||||||
kwargs={"pk": self.first_ad_view_reward_plan.uuid, "user_uuid": self.user_1_uuid})
|
|
||||||
# url = reverse('promotions-application:plan-test', kwargs={"pk": self.first_ad_view_reward_plan.uuid, "user_uuid":self.user_1_uuid})
|
|
||||||
# response = self.client.get(url, event_create_data, HTTP_AUTHORIZATION=auth,
|
|
||||||
# format='json')
|
|
||||||
# print(response.json())
|
|
||||||
# print(response.status_code)
|
|
||||||
# response = self.client.post(reverse('promotions:user-promote', kwargs={"pk":self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
response = self.client.post(url, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
response = self.client.post(url, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
self.first_ad_view_reward_plan.refresh_from_db()
|
|
||||||
self.assertEqual(self.first_ad_view_reward_plan.balance, 10000 - 800)
|
|
||||||
|
|
||||||
# response = self.client.get(reverse('promotions:promotion-list', kwargs={"plan": self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth,
|
|
||||||
# format='json')
|
|
||||||
# print(response.json())
|
|
||||||
|
|
||||||
# response = self.client.post(reverse('promotions:promotion-create', kwargs={"plan": self.first_ad_view_reward_plan.uuid}), event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
# print(response.json())
|
|
||||||
|
|
||||||
def test_application_promote_referral_reward_success(self):
|
|
||||||
self.referral_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.referral_reward_label,
|
|
||||||
event_label=self.referral_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.referral_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.referral_reward_label,
|
|
||||||
balance=10000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.referral_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.referral_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label=self.referral_reward_label,
|
|
||||||
plan=self.referral_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field="->event:referral",
|
|
||||||
base_amount_field="1000",
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.referral_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'referral': self.user_2_uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = reverse('promotions-application:application-user-plan-promote',
|
|
||||||
kwargs={
|
|
||||||
"pk": str(self.referral_reward_plan.uuid),
|
|
||||||
"user_uuid": str(self.user_1_uuid)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = self.client.post(url, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
self.referral_reward_plan.refresh_from_db()
|
|
||||||
self.assertEqual(self.referral_reward_plan.balance, 10000 - 1000)
|
|
||||||
self.assertEqual(response.json()['promotions'][0]['user'], str(self.user_2_uuid))
|
|
||||||
|
|
||||||
def test_application_get_promotion_plan_success(self):
|
|
||||||
self.first_ad_view_reward_event_saver, _created = EventSaver.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
event_label=self.first_ad_view_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.first_ad_view_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
balance=10000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.first_ad_view_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.first_ad_view_reward_recipient, _created = Recipient.objects.get_or_create(
|
|
||||||
label=self.first_ad_view_reward_label,
|
|
||||||
plan=self.first_ad_view_reward_plan,
|
|
||||||
wallet_uuid=self.user_1_uuid,
|
|
||||||
recipient_uuid_field="->event:user",
|
|
||||||
base_amount_field="event:base_amount",
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.first_ad_view_reward_label,
|
|
||||||
"data": {
|
|
||||||
'user': self.user_1_uuid,
|
|
||||||
'base_amount': 800
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url_promote = reverse('promotions-application:application-user-plan-promote',
|
|
||||||
kwargs={"pk": self.first_ad_view_reward_plan.uuid,
|
|
||||||
"user_uuid": '1f9c72e4-3cc3-420d-928b-b0a3d65a4611'})
|
|
||||||
response = self.client.post(url_promote, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
|
|
||||||
def test_application_user__user_not_fount__404(self):
|
|
||||||
self.first_ad_view_reward_plan, _created = Plan.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
balance=10000,
|
|
||||||
user=self.user_1,
|
|
||||||
application=self.application,
|
|
||||||
event_list=[self.first_ad_view_reward_label]
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
|
||||||
event_create_data = {}
|
|
||||||
|
|
||||||
url_promote = reverse('promotions-application:application-user-plan-promote',
|
|
||||||
kwargs={"pk": self.first_ad_view_reward_plan.uuid,
|
|
||||||
"user_uuid": '11111111-1111-1111-1111-111111111111'})
|
|
||||||
response = self.client.post(url_promote, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
print(response.json())
|
|
||||||
self.assertEqual(response.status_code, 404)
|
|
||||||
|
|
||||||
def test_application_event_viewset_create_success(self):
|
|
||||||
EventSaver.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
event_label=self.first_ad_view_reward_label,
|
|
||||||
save_once=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.first_ad_view_reward_label,
|
|
||||||
'data': {'foo': 'bar'},
|
|
||||||
}
|
|
||||||
|
|
||||||
url = reverse('promotions-application:event-list', kwargs={'user_uuid': str(self.user_1_uuid)})
|
|
||||||
response = self.client.post(url, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 201)
|
|
||||||
event = Event.objects.get(label=self.first_ad_view_reward_label)
|
|
||||||
self.assertEqual(event.user, self.user_1_uuid)
|
|
||||||
|
|
||||||
def test_application_event_viewset_create_duplicate_returns_unprocessable_entity(self):
|
|
||||||
EventSaver.objects.get_or_create(
|
|
||||||
title=self.first_ad_view_reward_label,
|
|
||||||
event_label=self.first_ad_view_reward_label,
|
|
||||||
save_once=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
auth = self._create_authorization_header(self.application_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': self.first_ad_view_reward_label,
|
|
||||||
'data': {'foo': 'bar'},
|
|
||||||
}
|
|
||||||
|
|
||||||
url = reverse('promotions-application:event-list', kwargs={'user_uuid': str(self.user_1_uuid)})
|
|
||||||
first_response = self.client.post(url, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
self.assertEqual(first_response.status_code, 201)
|
|
||||||
|
|
||||||
second_response = self.client.post(url, event_create_data, HTTP_AUTHORIZATION=auth, format='json')
|
|
||||||
self.assertEqual(second_response.status_code, 422)
|
|
||||||
|
|
||||||
def _create_first_ad_create_plan(self):
|
|
||||||
event_label = 'ads::first-ad-create'
|
|
||||||
plan_title = 'first-ad-create'
|
|
||||||
promotion_amount = 30000
|
|
||||||
|
|
||||||
EventSaver.objects.get_or_create(
|
|
||||||
event_label=event_label,
|
|
||||||
defaults={
|
|
||||||
'title': plan_title,
|
|
||||||
'save_once': True,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
plan, _ = Plan.objects.get_or_create(
|
|
||||||
title=plan_title,
|
|
||||||
defaults={
|
|
||||||
'balance': promotion_amount * 1000,
|
|
||||||
'user': self.user_1,
|
|
||||||
'application': self.application,
|
|
||||||
'event_list': [event_label],
|
|
||||||
'processor': ProcessorTypeChoices.OTHERS,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
Recipient.objects.update_or_create(
|
|
||||||
label=plan_title,
|
|
||||||
plan=plan,
|
|
||||||
defaults={
|
|
||||||
'recipient_uuid_field': '->event:user',
|
|
||||||
'base_amount_field': str(promotion_amount),
|
|
||||||
'wallet_destination': WalletDestinationChoices.ADVERTISING_TRANSIT,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return plan, event_label, promotion_amount
|
|
||||||
|
|
||||||
def test_first_ad_create_event_status_not_processed(self):
|
|
||||||
plan, event_label, promotion_amount = self._create_first_ad_create_plan()
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('promotions:event-status', kwargs={'event_label': event_label}),
|
|
||||||
HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.json(), {
|
|
||||||
'event_label': event_label,
|
|
||||||
'processed': False,
|
|
||||||
'promotion_amount': promotion_amount,
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_first_ad_create_event_status_processed(self):
|
|
||||||
plan, event_label, promotion_amount = self._create_first_ad_create_plan()
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
event_create_data = {
|
|
||||||
'label': event_label,
|
|
||||||
'data': {
|
|
||||||
'user': str(self.user_1_uuid),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch('apps.promotions.models.deposit_to_user_wallet_submit',
|
|
||||||
side_effect=mock_submit_deposit_success) as submit_mock:
|
|
||||||
response = self.client.post(
|
|
||||||
reverse('promotions:promotion-create', kwargs={'plan': plan.uuid}),
|
|
||||||
event_create_data,
|
|
||||||
HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json',
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, 201)
|
|
||||||
|
|
||||||
# first_ad_create is billboard/ad credit, not a cash-like user reward: the deposit
|
|
||||||
# is drawn from the promotions transit pool and credited to the advertising
|
|
||||||
# transit wallet, not the user's own reward wallet.
|
|
||||||
from django.conf import settings
|
|
||||||
submit_mock.assert_called_once()
|
|
||||||
call_payer_wallet, call_data = submit_mock.call_args.args
|
|
||||||
self.assertEqual(call_payer_wallet, settings.WALLET_PROMOTIONS_CREDIT)
|
|
||||||
self.assertEqual(call_data['payee_wallet'], settings.WALLET_ADVERTISING_TRANSIT)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('promotions:event-status', kwargs={'event_label': event_label}),
|
|
||||||
HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.json(), {
|
|
||||||
'event_label': event_label,
|
|
||||||
'processed': True,
|
|
||||||
'promotion_amount': None,
|
|
||||||
})
|
|
||||||
plan.refresh_from_db()
|
|
||||||
self.assertEqual(plan.balance, promotion_amount * 1000 - promotion_amount)
|
|
||||||
|
|
||||||
def test_get_wallet_category_uuid_routing(self):
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
default_recipient = Recipient()
|
|
||||||
self.assertEqual(default_recipient.get_wallet_category_uuid(),
|
|
||||||
settings.WALLET_USER_BILLBOARD_VISIT_INCOME)
|
|
||||||
|
|
||||||
user_reward_recipient = Recipient(wallet_destination=WalletDestinationChoices.USER_REWARD)
|
|
||||||
self.assertEqual(user_reward_recipient.get_wallet_category_uuid(),
|
|
||||||
settings.WALLET_USER_BILLBOARD_VISIT_INCOME)
|
|
||||||
|
|
||||||
transit_recipient = Recipient(wallet_destination=WalletDestinationChoices.ADVERTISING_TRANSIT)
|
|
||||||
self.assertEqual(transit_recipient.get_wallet_category_uuid(), settings.WALLET_ADVERTISING_TRANSIT)
|
|
||||||
|
|
||||||
explicit_wallet_uuid = uuid.uuid4()
|
|
||||||
override_recipient = Recipient(wallet_destination=WalletDestinationChoices.ADVERTISING_TRANSIT,
|
|
||||||
wallet_uuid=explicit_wallet_uuid)
|
|
||||||
self.assertEqual(override_recipient.get_wallet_category_uuid(), explicit_wallet_uuid)
|
|
||||||
|
|
||||||
def test_first_ad_create_event_status_not_processed_when_only_event_exists(self):
|
|
||||||
plan, event_label, promotion_amount = self._create_first_ad_create_plan()
|
|
||||||
auth = self._create_authorization_header(self.user_access_token.token)
|
|
||||||
Event.objects.create(
|
|
||||||
user=self.user_1_uuid,
|
|
||||||
application=self.application,
|
|
||||||
label=event_label,
|
|
||||||
data={'user': str(self.user_1_uuid)},
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.client.get(
|
|
||||||
reverse('promotions:event-status', kwargs={'event_label': event_label}),
|
|
||||||
HTTP_AUTHORIZATION=auth,
|
|
||||||
format='json',
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.json(), {
|
|
||||||
'event_label': event_label,
|
|
||||||
'processed': False,
|
|
||||||
'promotion_amount': promotion_amount,
|
|
||||||
})
|
|
||||||
|
|
|
||||||
22
apps/promotions/urls.py
Normal file
22
apps/promotions/urls.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
from rest_framework import routers
|
||||||
|
from rest_framework.routers import DefaultRouter, DynamicRoute, Route
|
||||||
|
|
||||||
|
from utils.router import ProfileRouter
|
||||||
|
from . import views
|
||||||
|
from django.urls import NoReverseMatch, path, re_path, include
|
||||||
|
|
||||||
|
app_name = 'promotions'
|
||||||
|
|
||||||
|
application_router = DefaultRouter()
|
||||||
|
event_router = DefaultRouter()
|
||||||
|
application_router.register(r'promotion', views.ApplicationPromotionViewSet, basename='application_promotions' )
|
||||||
|
event_router.register(r'events', views.ApplicationEventViewSet, basename='application_events' )
|
||||||
|
|
||||||
|
user_router = DefaultRouter()
|
||||||
|
user_router.register('plan', views.UserPlanViewSet, basename='user')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('api/v1/application/<user_uuid>/', include(application_router.urls), name='application'),
|
||||||
|
path('api/v1/application/', include(event_router.urls), name='events'),
|
||||||
|
path('api/v1/user/', include(user_router.urls), name='user'),
|
||||||
|
]
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
from rest_framework import routers
|
|
||||||
from rest_framework.routers import DefaultRouter, DynamicRoute, Route
|
|
||||||
from . import views_application
|
|
||||||
from django.urls import NoReverseMatch, path, re_path
|
|
||||||
|
|
||||||
|
|
||||||
app_name = 'promotions-application'
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationRouter(DefaultRouter):
|
|
||||||
|
|
||||||
routes = [
|
|
||||||
# List route.
|
|
||||||
Route(
|
|
||||||
url=r'^{prefix}/<user_uuid>{trailing_slash}$',
|
|
||||||
mapping={
|
|
||||||
'get': 'list',
|
|
||||||
'post': 'create'
|
|
||||||
},
|
|
||||||
name='{basename}-list',
|
|
||||||
detail=False,
|
|
||||||
initkwargs={'suffix': 'List'}
|
|
||||||
),
|
|
||||||
# Dynamically generated list routes. Generated using
|
|
||||||
# @action(detail=False) decorator on methods of the viewset.
|
|
||||||
DynamicRoute(
|
|
||||||
url=r'^{prefix}/<user_uuid>/{url_path}{trailing_slash}$',
|
|
||||||
name='{basename}-{url_name}',
|
|
||||||
detail=False,
|
|
||||||
initkwargs={}
|
|
||||||
),
|
|
||||||
# # Detail route.
|
|
||||||
Route(
|
|
||||||
url=r'^{prefix}/<user_uuid>/{lookup}{trailing_slash}$',
|
|
||||||
mapping={
|
|
||||||
'get': 'retrieve',
|
|
||||||
'put': 'update',
|
|
||||||
'patch': 'partial_update',
|
|
||||||
'delete': 'destroy'
|
|
||||||
},
|
|
||||||
name='{basename}-detail',
|
|
||||||
detail=True,
|
|
||||||
initkwargs={'suffix': 'Instance'}
|
|
||||||
),
|
|
||||||
# # Dynamically generated detail routes. Generated using
|
|
||||||
# # @action(detail=True) decorator on methods of the viewset.
|
|
||||||
DynamicRoute(
|
|
||||||
url=r'^{prefix}/<user_uuid>/{lookup}/{url_path}{trailing_slash}$',
|
|
||||||
name='{basename}-{url_name}',
|
|
||||||
detail=True,
|
|
||||||
initkwargs={}
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
router = DefaultRouter()
|
|
||||||
|
|
||||||
router.register('plan', views_application.ApplicationUserPlanViewSet, basename='application-user-plan')
|
|
||||||
# router.register('plan', views_application.ApplicationPlanViewSet, basename='application-plan')
|
|
||||||
router.register('event', views_application.ApplicationEventViewSet, basename='event')
|
|
||||||
|
|
||||||
urlpatterns = router.urls
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
from rest_framework import routers
|
|
||||||
from rest_framework.routers import DefaultRouter, DynamicRoute, Route
|
|
||||||
|
|
||||||
from utils.router import ProfileRouter
|
|
||||||
from . import views_user
|
|
||||||
from django.urls import NoReverseMatch, path, re_path, include
|
|
||||||
|
|
||||||
from .views_user import (
|
|
||||||
ApplicationPromoteUserApiView,
|
|
||||||
ApplicationEventSubmitAPIView,
|
|
||||||
ApplicationPromotionListApiView,
|
|
||||||
ApplicationEventStatusApiView,
|
|
||||||
)
|
|
||||||
|
|
||||||
app_name = 'promotions'
|
|
||||||
|
|
||||||
router = DefaultRouter()
|
|
||||||
# router.register(r'application', views.ApplicationPromotionViewSet, basename='application_promotions')
|
|
||||||
# router.register(r'events', views.ApplicationViewSet, basename='application-events')
|
|
||||||
router.register(r'plans', views_user.UserPlanViewSet, basename='user-plans')
|
|
||||||
|
|
||||||
# urlpatterns =
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('api/v1/plans/<plan>/', ApplicationPromoteUserApiView.as_view(), name='promotion-create'),
|
|
||||||
path('api/v1/plans/<plan>/promotins', ApplicationPromotionListApiView.as_view(), name='promotion-list'),
|
|
||||||
path('api/v1/events/<path:event_label>/status/', ApplicationEventStatusApiView.as_view(), name='event-status'),
|
|
||||||
path('api/v1/events/submit', ApplicationEventSubmitAPIView.as_view(), name='event-create'),
|
|
||||||
# path('api/v1/events/get', ApplicationEventRetrieveAPIView.as_view(), name='event-retrieve'),
|
|
||||||
path('api/v2/', include(router.urls)),
|
|
||||||
]
|
|
||||||
118
apps/promotions/views.py
Normal file
118
apps/promotions/views.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
from drf_spectacular.utils import extend_schema
|
||||||
|
from rest_framework import mixins
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||||
|
from apps.gooyal_oauth2.utils import get_application
|
||||||
|
from .models import Plan, Promotion
|
||||||
|
from .serializers import PlanSerializer, PromotionSerializer, EventSerializer
|
||||||
|
from .tasks import analyze_event_task
|
||||||
|
from ..users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
# class ApplicationPlanViewSet(mixins.ListModelMixin,
|
||||||
|
# mixins.CreateModelMixin,
|
||||||
|
# GenericViewSet):
|
||||||
|
# queryset = Plan.objects.all()
|
||||||
|
# serializer_class = PlanSerializer
|
||||||
|
#
|
||||||
|
# permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
# required_alternate_scopes = {
|
||||||
|
# "POST": [["promotions.application.user-plans:submit"]],
|
||||||
|
# "GET": [["promotions.application.user-promotions:list-retrieve"]],
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# def get_queryset(self):
|
||||||
|
# user_uuid = self.kwargs.get('user_uuid')
|
||||||
|
# user = User.objects.get(uuid=user_uuid)
|
||||||
|
# application = get_application(self.request)
|
||||||
|
# return Plan.objects.filter(application=application)
|
||||||
|
#
|
||||||
|
|
||||||
|
class ApplicationPromotionViewSet(
|
||||||
|
mixins.RetrieveModelMixin,
|
||||||
|
mixins.ListModelMixin,
|
||||||
|
mixins.CreateModelMixin,
|
||||||
|
GenericViewSet
|
||||||
|
):
|
||||||
|
serializer_class = PromotionSerializer
|
||||||
|
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
required_alternate_scopes = {
|
||||||
|
"POST": [["promotions.application.user-promotions:promote"]],
|
||||||
|
"GET": [["promotions.application.user-promotions:list-retrieve"]],
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user_uuid = self.kwargs.get('user_uuid')
|
||||||
|
user = User.objects.get(uuid=user_uuid)
|
||||||
|
application = get_application(self.request)
|
||||||
|
return Promotion.objects.filter(application=application, user=user)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
user_uuid = self.kwargs.get('user_uuid')
|
||||||
|
user = User.objects.get(uuid=user_uuid)
|
||||||
|
application = get_application(self.request)
|
||||||
|
plan = serializer.validated_data['plan']
|
||||||
|
|
||||||
|
promotion_args = dict(user=user, application=application, **serializer.validated_data)
|
||||||
|
promotion_amount = plan.calculate_promotion(**promotion_args)
|
||||||
|
if promotion_amount:
|
||||||
|
promotion = serializer.save(promotion_amount=promotion_amount, user=user, application=application)
|
||||||
|
promotion.promote(**promotion_args)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationEventViewSet(
|
||||||
|
mixins.CreateModelMixin,
|
||||||
|
GenericViewSet
|
||||||
|
):
|
||||||
|
serializer_class = EventSerializer
|
||||||
|
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
required_alternate_scopes = {
|
||||||
|
"POST": [["promotions.application.event:submit"]],
|
||||||
|
}
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
if hasattr(self.request, 'user'):
|
||||||
|
user = self.request.user
|
||||||
|
else:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
application = get_application(self.request)
|
||||||
|
event = serializer.save(user=user, application=application)
|
||||||
|
analyze_event_task.delay(event.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UserPlanViewSet(mixins.RetrieveModelMixin,
|
||||||
|
mixins.ListModelMixin,
|
||||||
|
GenericViewSet):
|
||||||
|
serializer_class = PlanSerializer
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
required_alternate_scopes = {
|
||||||
|
"GET": [["promotions.user.self-plans:list-retrieve"]],
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = self.request.user
|
||||||
|
application = get_application(self.request)
|
||||||
|
|
||||||
|
queryset = Plan.objects.all()
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
@extend_schema(responses=PlanSerializer(many=True))
|
||||||
|
@action(detail=False, methods=['POST'], serializer_class=PlanSerializer)
|
||||||
|
def available(self, request):
|
||||||
|
serializer = self.get_serializer(data=self.request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
print(serializer.validated_data)
|
||||||
|
plans = self.paginate_queryset(self.filter_queryset(self.get_queryset()))
|
||||||
|
for plan in plans:
|
||||||
|
plan.calculate_promotion(**serializer.validated_data)
|
||||||
|
serializer = self.get_serializer(plans, many=True)
|
||||||
|
return self.get_paginated_response(serializer.data)
|
||||||
|
|
@ -1,357 +0,0 @@
|
||||||
from drf_spectacular.utils import extend_schema
|
|
||||||
from rest_framework import mixins, status
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework import exceptions
|
|
||||||
from rest_framework.exceptions import NotFound
|
|
||||||
from rest_framework.generics import CreateAPIView, get_object_or_404, RetrieveAPIView, ListAPIView
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.settings import api_settings
|
|
||||||
from rest_framework.viewsets import GenericViewSet
|
|
||||||
|
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements, TokenMatchesViewSetActions
|
|
||||||
from apps.gooyal_oauth2.utils import get_application
|
|
||||||
from utils.clients.accounts_client import get_user_info
|
|
||||||
from utils.exceptions import UnprocessableEntity
|
|
||||||
from .models import Plan, Promotion, EventSaver, get_event_status_for_user, rollback_promotion_payout
|
|
||||||
from .serializers import (
|
|
||||||
PlanSerializer,
|
|
||||||
PromotionSerializer,
|
|
||||||
EventSerializer,
|
|
||||||
PromoteSerializer,
|
|
||||||
UserPlanSerializer,
|
|
||||||
PromotionStatusSerializer,
|
|
||||||
PromotionRollbackSerializer,
|
|
||||||
)
|
|
||||||
from .tasks import analyze_event_task
|
|
||||||
from ..users.models import User
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationUserPlanViewSet(
|
|
||||||
mixins.RetrieveModelMixin,
|
|
||||||
mixins.ListModelMixin,
|
|
||||||
# mixins.CreateModelMixin,
|
|
||||||
GenericViewSet
|
|
||||||
):
|
|
||||||
serializer_class = UserPlanSerializer
|
|
||||||
|
|
||||||
permission_classes = [TokenMatchesViewSetActions]
|
|
||||||
required_action_scopes = {
|
|
||||||
"promote": [["promotions.application.user-plan:promote"]],
|
|
||||||
"retrieve": [["promotions.application.user-plan:list-retrieve"]],
|
|
||||||
"list": [["promotions.application.user-plan:list-retrieve"]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
application = get_application(self.request)
|
|
||||||
if not self.request.user:
|
|
||||||
user_uuid = self.kwargs.get('user_uuid')
|
|
||||||
user = User.objects.filter(uuid=user_uuid).first()
|
|
||||||
if not user:
|
|
||||||
user_info = get_user_info(user_uuid)
|
|
||||||
if user_info:
|
|
||||||
user, created = User.objects.get_or_create(uuid=user_uuid)
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise NotFound(_('user {user_uuid} not found').format(user_uuid=user_uuid))
|
|
||||||
|
|
||||||
self.request.user = user
|
|
||||||
# return Promotion.objects.filter(application=application)
|
|
||||||
return Plan.objects.all()
|
|
||||||
|
|
||||||
# def perform_create(self, serializer):
|
|
||||||
# user = self.get_user()
|
|
||||||
# application = get_application(self.request)
|
|
||||||
#
|
|
||||||
# plan = serializer.validated_data['plan']
|
|
||||||
#
|
|
||||||
# promotion_args = dict(user=user, application=application, **serializer.validated_data)
|
|
||||||
# promotion_amount = plan.calculate_promotion(**promotion_args)
|
|
||||||
# if promotion_amount:
|
|
||||||
# promotion = serializer.save(promotion_amount=promotion_amount, user=user, application=application)
|
|
||||||
# promotion.promote(**promotion_args)
|
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'], serializer_class=PromoteSerializer)
|
|
||||||
def promote(self, request, *args, pk=None, **kwargs):
|
|
||||||
plan = self.get_object()
|
|
||||||
application = get_application(self.request)
|
|
||||||
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.validated_data
|
|
||||||
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# try:
|
|
||||||
|
|
||||||
event = event_saver.save_event(user=self.request.user.pk, application=application, **serializer.validated_data)
|
|
||||||
|
|
||||||
# except Exception as e:
|
|
||||||
# raise exceptions.ValidationError(str(e))
|
|
||||||
|
|
||||||
result_list = plan.process_event(event)
|
|
||||||
data["promotions"] = result_list
|
|
||||||
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
def get_success_headers(self, data):
|
|
||||||
try:
|
|
||||||
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
|
|
||||||
except (TypeError, KeyError):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationEventViewSet(
|
|
||||||
mixins.CreateModelMixin,
|
|
||||||
GenericViewSet
|
|
||||||
):
|
|
||||||
serializer_class = EventSerializer
|
|
||||||
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"POST": [["promotions.application.event:submit"]],
|
|
||||||
"GET": [["promotions.application.event:submit"]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _resolve_user(self):
|
|
||||||
user = getattr(self.request, 'user', None)
|
|
||||||
if user and getattr(user, 'uuid', None):
|
|
||||||
return user
|
|
||||||
|
|
||||||
user_uuid = self.kwargs.get('user_uuid')
|
|
||||||
user = User.objects.filter(uuid=user_uuid).first()
|
|
||||||
if not user:
|
|
||||||
user_info = get_user_info(user_uuid)
|
|
||||||
if user_info:
|
|
||||||
user, _ = User.objects.get_or_create(uuid=user_uuid)
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise NotFound(_('user {user_uuid} not found').format(user_uuid=user_uuid))
|
|
||||||
|
|
||||||
self.request.user = user
|
|
||||||
return user
|
|
||||||
|
|
||||||
@action(
|
|
||||||
detail=False,
|
|
||||||
methods=['GET'],
|
|
||||||
url_path=r'(?P<event_label>.+)/status',
|
|
||||||
serializer_class=PromotionStatusSerializer,
|
|
||||||
)
|
|
||||||
def status(self, request, user_uuid=None, event_label=None):
|
|
||||||
user = self._resolve_user()
|
|
||||||
serializer = self.get_serializer(get_event_status_for_user(user.uuid, event_label))
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
@action(
|
|
||||||
detail=False,
|
|
||||||
methods=['POST'],
|
|
||||||
url_path=r'(?P<event_label>.+)/rollback',
|
|
||||||
serializer_class=PromotionRollbackSerializer,
|
|
||||||
)
|
|
||||||
def rollback(self, request, user_uuid=None, event_label=None):
|
|
||||||
"""Pull a promotion's payout back out of the advertising transit wallet.
|
|
||||||
|
|
||||||
Idempotent. The promotion stays "used" -- only the money is returned,
|
|
||||||
and only if it landed in the transit wallet (a payout straight to the
|
|
||||||
user's wallet is not reversible). If the payout has not been processed
|
|
||||||
yet the request is recorded and the payout is suppressed when it runs.
|
|
||||||
"""
|
|
||||||
user = self._resolve_user()
|
|
||||||
state, amount = rollback_promotion_payout(user.uuid, event_label)
|
|
||||||
|
|
||||||
serializer = self.get_serializer({
|
|
||||||
'event_label': event_label,
|
|
||||||
'status': state,
|
|
||||||
'amount': amount,
|
|
||||||
})
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
def perform_create(self, serializer: EventSerializer):
|
|
||||||
user = self._resolve_user()
|
|
||||||
|
|
||||||
application = get_application(self.request)
|
|
||||||
# EventSaver.objects.first(event_label=serializer.validated_data['event_label'], valid_application__contains=application.uuid)
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
try:
|
|
||||||
event = event_saver.save_event(user=user.uuid, application=application, **serializer.validated_data)
|
|
||||||
except Exception as e:
|
|
||||||
raise UnprocessableEntity(str(e))
|
|
||||||
|
|
||||||
analyze_event_task.delay(event.uuid)
|
|
||||||
serializer.instance = event
|
|
||||||
|
|
||||||
class ApplicationPlanViewSet(
|
|
||||||
GenericViewSet
|
|
||||||
):
|
|
||||||
serializer_class = PlanSerializer
|
|
||||||
|
|
||||||
permission_classes = [TokenMatchesViewSetActions]
|
|
||||||
required_action_scopes = {
|
|
||||||
"retrieve": [["promotions.application.plan:list-retrieve"]],
|
|
||||||
"list": [["promotions.application.plan:list-retrieve"]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
application = get_application(self.request)
|
|
||||||
# return Promotion.objects.filter(application=application)
|
|
||||||
return Plan.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# NOT
|
|
||||||
class UserPlanViewSet(mixins.RetrieveModelMixin,
|
|
||||||
# mixins.ListModelMixin,
|
|
||||||
# mixins.CreateModelMixin,
|
|
||||||
GenericViewSet):
|
|
||||||
serializer_class = UserPlanSerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
# required_alternate_scopes = {
|
|
||||||
# "GET": [["promotions.user.plans:list-retrieve"]],
|
|
||||||
# "POST": [["promotions.user.plans:list-retrieve"]],
|
|
||||||
# }
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"GET": [[]],
|
|
||||||
"POST": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user
|
|
||||||
application = get_application(self.request)
|
|
||||||
|
|
||||||
queryset = Plan.objects.all()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# @extend_schema(responses=PlanSerializer(many=True))
|
|
||||||
# @action(detail=False, methods=['POST'], serializer_class=PlanSerializer)
|
|
||||||
# def available(self, request):
|
|
||||||
# serializer = self.get_serializer(data=self.request.data)
|
|
||||||
# serializer.is_valid(raise_exception=True)
|
|
||||||
# print(serializer.validated_data)
|
|
||||||
# plans = self.paginate_queryset(self.filter_queryset(self.get_queryset()))
|
|
||||||
# for plan in plans:
|
|
||||||
# plan.calculate_promotion(**serializer.validated_data)
|
|
||||||
# serializer = self.get_serializer(plans, many=True)
|
|
||||||
# return self.get_paginated_response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPromoteUserApiView(CreateAPIView):
|
|
||||||
model = Promotion
|
|
||||||
serializer_class = PromoteSerializer
|
|
||||||
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"POST": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_plan(self):
|
|
||||||
plan_uuid = self.kwargs.get('plan')
|
|
||||||
plan = get_object_or_404(Plan, uuid=plan_uuid)
|
|
||||||
return plan
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
plan = self.get_plan()
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.validated_data
|
|
||||||
application = get_application(self.request)
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
user = self.request.user.pk or serializer.validated_data['user']
|
|
||||||
# try:
|
|
||||||
event = event_saver.save_event(user=user, application=application, **serializer.validated_data)
|
|
||||||
# except Exception as e:
|
|
||||||
# raise exceptions.ValidationError(str(e))
|
|
||||||
|
|
||||||
result_list = list(plan.process_event(event))
|
|
||||||
# data["promotions"] = list(result_list)
|
|
||||||
|
|
||||||
serializer = self.get_serializer(instance=plan)
|
|
||||||
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPromotionListApiView(ListAPIView):
|
|
||||||
model = Promotion
|
|
||||||
serializer_class = PromotionSerializer
|
|
||||||
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"GET": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_plan(self):
|
|
||||||
plan_uuid = self.kwargs.get('plan')
|
|
||||||
plan = get_object_or_404(Plan, uuid=plan_uuid)
|
|
||||||
return plan
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user
|
|
||||||
plan = self.get_plan()
|
|
||||||
qs = Promotion.objects.filter(user_uuid=user.uuid, plan=plan)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
# def create(self, request, *args, **kwargs):
|
|
||||||
# plan = self.get_plan()
|
|
||||||
# serializer = self.get_serializer(data=request.data)
|
|
||||||
# serializer.is_valid(raise_exception=True)
|
|
||||||
# data = serializer.validated_data
|
|
||||||
# application = get_application(self.request)
|
|
||||||
# event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
# # try:
|
|
||||||
# event = event_saver.save_event(user=None, application=application, **serializer.validated_data)
|
|
||||||
# # except Exception as e:
|
|
||||||
# # raise exceptions.ValidationError(str(e))
|
|
||||||
#
|
|
||||||
# result_list = plan.process_event(event)
|
|
||||||
# data["promotions"] = result_list
|
|
||||||
#
|
|
||||||
# headers = self.get_success_headers(serializer.data)
|
|
||||||
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationEventSubmitAPIView(CreateAPIView):
|
|
||||||
model = Promotion
|
|
||||||
serializer_class = PromoteSerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"POST": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.validated_data
|
|
||||||
application = get_application(self.request)
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
user = self.request.user.pk or serializer.validated_data['user']
|
|
||||||
try:
|
|
||||||
event = event_saver.save_event(user=user, application=application, **serializer.validated_data)
|
|
||||||
except Exception as e:
|
|
||||||
raise UnprocessableEntity(str(e))
|
|
||||||
|
|
||||||
analyze_event_task.delay(event.uuid)
|
|
||||||
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
# class ApplicationEventRetrieveAPIView(RetrieveAPIView):
|
|
||||||
# model = Promotion
|
|
||||||
# serializer_class = PromoteSerializer
|
|
||||||
#
|
|
||||||
# def create(self, request, *args, **kwargs):
|
|
||||||
# serializer = self.get_serializer(data=request.data)
|
|
||||||
# serializer.is_valid(raise_exception=True)
|
|
||||||
# data = serializer.validated_data
|
|
||||||
# application = get_application(self.request)
|
|
||||||
# event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
# event = event_saver.save_event(user=None, application=application, **serializer.validated_data)
|
|
||||||
#
|
|
||||||
# analyze_event_task.delay(event.uuid)
|
|
||||||
#
|
|
||||||
# headers = self.get_success_headers(serializer.data)
|
|
||||||
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
#
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
||||||
from drf_spectacular.utils import extend_schema
|
|
||||||
from rest_framework import mixins, status
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework import exceptions
|
|
||||||
from rest_framework.generics import CreateAPIView, get_object_or_404, RetrieveAPIView, ListAPIView
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.viewsets import GenericViewSet
|
|
||||||
|
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
|
||||||
from apps.gooyal_oauth2.utils import get_application
|
|
||||||
from utils.exceptions import UnprocessableEntity
|
|
||||||
from .models import Plan, Promotion, EventSaver, get_event_status_for_user
|
|
||||||
from .serializers import (
|
|
||||||
PlanSerializer,
|
|
||||||
PromotionSerializer,
|
|
||||||
EventSerializer,
|
|
||||||
PromoteSerializer,
|
|
||||||
UserPlanSerializer,
|
|
||||||
PromotionStatusSerializer,
|
|
||||||
)
|
|
||||||
from .tasks import analyze_event_task
|
|
||||||
from ..users.models import User
|
|
||||||
|
|
||||||
|
|
||||||
class UserPlanViewSet(mixins.RetrieveModelMixin,
|
|
||||||
# mixins.ListModelMixin,
|
|
||||||
# mixins.CreateModelMixin,
|
|
||||||
GenericViewSet):
|
|
||||||
serializer_class = UserPlanSerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
# required_alternate_scopes = {
|
|
||||||
# "GET": [["promotions.user.plans:list-retrieve"]],
|
|
||||||
# "POST": [["promotions.user.plans:list-retrieve"]],
|
|
||||||
# }
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"GET": [[]],
|
|
||||||
"POST": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user
|
|
||||||
application = get_application(self.request)
|
|
||||||
|
|
||||||
queryset = Plan.objects.all()
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
# @extend_schema(responses=PlanSerializer(many=True))
|
|
||||||
# @action(detail=False, methods=['POST'], serializer_class=PlanSerializer)
|
|
||||||
# def available(self, request):
|
|
||||||
# serializer = self.get_serializer(data=self.request.data)
|
|
||||||
# serializer.is_valid(raise_exception=True)
|
|
||||||
# print(serializer.validated_data)
|
|
||||||
# plans = self.paginate_queryset(self.filter_queryset(self.get_queryset()))
|
|
||||||
# for plan in plans:
|
|
||||||
# plan.calculate_promotion(**serializer.validated_data)
|
|
||||||
# serializer = self.get_serializer(plans, many=True)
|
|
||||||
# return self.get_paginated_response(serializer.data)
|
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'], serializer_class=PromoteSerializer)
|
|
||||||
def promote(self, request, pk=None):
|
|
||||||
plan = self.get_object()
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.validated_data
|
|
||||||
application = get_application(self.request)
|
|
||||||
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
|
|
||||||
user = self.request.user.pk or serializer.validated_data['user']
|
|
||||||
# try:
|
|
||||||
event = event_saver.save_event(user=user, application=application, **serializer.validated_data)
|
|
||||||
# except Exception as e:
|
|
||||||
# raise exceptions.ValidationError(str(e))
|
|
||||||
|
|
||||||
result_list = plan.process_event(event)
|
|
||||||
data["promotions"] = result_list
|
|
||||||
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPromoteUserApiView(CreateAPIView):
|
|
||||||
model = Promotion
|
|
||||||
serializer_class = PromoteSerializer
|
|
||||||
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"POST": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_plan(self):
|
|
||||||
plan_uuid = self.kwargs.get('plan')
|
|
||||||
plan = get_object_or_404(Plan, uuid=plan_uuid)
|
|
||||||
return plan
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
plan = self.get_plan()
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.validated_data
|
|
||||||
application = get_application(self.request)
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
user = self.request.user.pk or serializer.validated_data['user']
|
|
||||||
# try:
|
|
||||||
event = event_saver.save_event(user=user, application=application, **serializer.validated_data)
|
|
||||||
# except Exception as e:
|
|
||||||
# raise exceptions.ValidationError(str(e))
|
|
||||||
|
|
||||||
result_list = list(plan.process_event(event))
|
|
||||||
# data["promotions"] = list(result_list)
|
|
||||||
|
|
||||||
serializer = self.get_serializer(instance=plan)
|
|
||||||
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
class ApplicationEventStatusApiView(RetrieveAPIView):
|
|
||||||
serializer_class = PromotionStatusSerializer
|
|
||||||
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"GET": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
|
||||||
event_label = kwargs['event_label']
|
|
||||||
user_uuid = request.user.uuid
|
|
||||||
serializer = self.get_serializer(get_event_status_for_user(user_uuid, event_label))
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPromotionListApiView(ListAPIView):
|
|
||||||
model = Promotion
|
|
||||||
serializer_class = PromotionSerializer
|
|
||||||
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"GET": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_plan(self):
|
|
||||||
plan_uuid = self.kwargs.get('plan')
|
|
||||||
plan = get_object_or_404(Plan, uuid=plan_uuid)
|
|
||||||
return plan
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user
|
|
||||||
plan = self.get_plan()
|
|
||||||
qs = Promotion.objects.filter(user_uuid=user.uuid, plan=plan)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
# def create(self, request, *args, **kwargs):
|
|
||||||
# plan = self.get_plan()
|
|
||||||
# serializer = self.get_serializer(data=request.data)
|
|
||||||
# serializer.is_valid(raise_exception=True)
|
|
||||||
# data = serializer.validated_data
|
|
||||||
# application = get_application(self.request)
|
|
||||||
# event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
# # try:
|
|
||||||
# event = event_saver.save_event(user=None, application=application, **serializer.validated_data)
|
|
||||||
# # except Exception as e:
|
|
||||||
# # raise exceptions.ValidationError(str(e))
|
|
||||||
#
|
|
||||||
# result_list = plan.process_event(event)
|
|
||||||
# data["promotions"] = result_list
|
|
||||||
#
|
|
||||||
# headers = self.get_success_headers(serializer.data)
|
|
||||||
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationEventSubmitAPIView(CreateAPIView):
|
|
||||||
model = Promotion
|
|
||||||
serializer_class = PromoteSerializer
|
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
|
||||||
required_alternate_scopes = {
|
|
||||||
"POST": [[]],
|
|
||||||
}
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
data = serializer.validated_data
|
|
||||||
application = get_application(self.request)
|
|
||||||
event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
user = self.request.user.pk or serializer.validated_data['user']
|
|
||||||
try:
|
|
||||||
event = event_saver.save_event(user=user, application=application, **serializer.validated_data)
|
|
||||||
except Exception as e:
|
|
||||||
raise UnprocessableEntity(str(e))
|
|
||||||
|
|
||||||
analyze_event_task.delay(event.uuid)
|
|
||||||
|
|
||||||
headers = self.get_success_headers(serializer.data)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
|
|
||||||
# class ApplicationEventRetrieveAPIView(RetrieveAPIView):
|
|
||||||
# model = Promotion
|
|
||||||
# serializer_class = PromoteSerializer
|
|
||||||
#
|
|
||||||
# def create(self, request, *args, **kwargs):
|
|
||||||
# serializer = self.get_serializer(data=request.data)
|
|
||||||
# serializer.is_valid(raise_exception=True)
|
|
||||||
# data = serializer.validated_data
|
|
||||||
# application = get_application(self.request)
|
|
||||||
# event_saver: EventSaver = EventSaver.objects.filter(event_label=serializer.validated_data['label']).first()
|
|
||||||
# event = event_saver.save_event(user=None, application=application, **serializer.validated_data)
|
|
||||||
#
|
|
||||||
# analyze_event_task.delay(event.uuid)
|
|
||||||
#
|
|
||||||
# headers = self.get_success_headers(serializer.data)
|
|
||||||
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
|
||||||
#
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
# Wallet Refactor — 2026-08-25
|
|
||||||
|
|
||||||
Brings promotions' wallet settings and deposit routing in line with the naming/routing
|
|
||||||
convention already rolled out to `advertising`, `settlement`, and `ipg`. No behavior in
|
|
||||||
those sibling repos changed as part of this — this document covers promotions only, with
|
|
||||||
the sibling commits cited as precedent for why the shape of the fix looks the way it does.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. The bug
|
|
||||||
|
|
||||||
`Promotion.promote()` (`apps/promotions/models.py`) submits every payout as a wallet
|
|
||||||
deposit. A deposit call takes two wallet references:
|
|
||||||
|
|
||||||
- `payer_wallet` — the call-target / URL param — the **company-side** pool the money is
|
|
||||||
drawn from.
|
|
||||||
- `payee_wallet` — a field in the request body — the **user-side** wallet type the
|
|
||||||
recipient is credited into.
|
|
||||||
|
|
||||||
Before this change, both were the same setting:
|
|
||||||
|
|
||||||
```python
|
|
||||||
"payee_wallet": settings.WALLET_REWARD,
|
|
||||||
...
|
|
||||||
submit_response = deposit_to_user_wallet_submit(settings.WALLET_REWARD, data)
|
|
||||||
...
|
|
||||||
verify_response = deposit_to_user_wallet_verify(settings.WALLET_REWARD, submit_response.uuid)
|
|
||||||
```
|
|
||||||
|
|
||||||
So every promotion payout was routed **out of and into the same wallet type** — there was
|
|
||||||
no real company-owned pool distinct from the user-side wallet category. This is the same
|
|
||||||
defect fixed in `settlement` (commit `cc2ffb9`, 2026-08-16):
|
|
||||||
|
|
||||||
> "Withdraw requests were passing the user's own source wallet (WALLET_RIAL/WALLET_REWARD)
|
|
||||||
> as the withdrawal's destination wallet param, so every settlement payout and commission
|
|
||||||
> was routed back into the same wallet type it came from."
|
|
||||||
|
|
||||||
and still open, but flagged, in `advertising`'s `wallet_service_integration.md` §6.2 for
|
|
||||||
`AdPayment.refund_balance()`.
|
|
||||||
|
|
||||||
A second, smaller bug rode along: `Recipient.get_wallet_category_uuid()` fell back to
|
|
||||||
`settings.WALLET_PROMOTION_CATEGORY_UUID` — a setting that was never defined anywhere in
|
|
||||||
`main/settings.py`. It happened to never be called from the live deposit path (which
|
|
||||||
hardcoded `WALLET_REWARD` instead), so this was latent, not yet crashing anything — but it
|
|
||||||
would have raised `AttributeError` the moment anyone wired it in, which is exactly what
|
|
||||||
this change does.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. The fix
|
|
||||||
|
|
||||||
### 2.1 Settings renamed to match the shared cross-repo convention
|
|
||||||
|
|
||||||
The two user-side wallet-type UUIDs are the same wallet-service UUIDs referenced by
|
|
||||||
`advertising`, `settlement`, and `ipg` — they're renamed to match those repos' naming
|
|
||||||
(`advertising`, then propagated to `settlement` in `cc2ffb9` and `ipg` in `7dcb730`):
|
|
||||||
|
|
||||||
| Before | After |
|
|
||||||
|---|---|
|
|
||||||
| `WALLET_RIAL` | `WALLET_RIAL_DEPOSIT` |
|
|
||||||
| `WALLET_REWARD` | `WALLET_USER_BILLBOARD_VISIT_INCOME` |
|
|
||||||
| *(did not exist)* | `WALLET_PROMOTIONS_CREDIT` — new |
|
|
||||||
|
|
||||||
`WALLET_RIAL_DEPOSIT` isn't read by any code path in promotions today (it wasn't before
|
|
||||||
either, under its old name) — it's renamed for consistency and in case a future feature
|
|
||||||
here needs to read a user's rial balance via `get_user_wallets()`.
|
|
||||||
|
|
||||||
### 2.2 A dedicated company-side wallet for payouts
|
|
||||||
|
|
||||||
`WALLET_PROMOTIONS_CREDIT` is new: the company pool promotion payouts are drawn from,
|
|
||||||
used only as `payer_wallet` (the deposit call-target). It is never the same UUID as any
|
|
||||||
user-side wallet type. This is the promotions equivalent of `WALLET_SETTLEMENT_TRANSIT`
|
|
||||||
(settlement) / `WALLET_ADVERTISING_TRANSIT` (advertising).
|
|
||||||
|
|
||||||
**This is a required new environment variable.** It ships in `.env.example` as a
|
|
||||||
placeholder UUID (`00000000-...`) with a `TODO(you)` comment — same pattern as
|
|
||||||
settlement's `.env.example`. **It needs a real wallet-type UUID provisioned by the
|
|
||||||
wallet-service team before this service can submit a deposit in any environment**, and
|
|
||||||
your local/staging/prod `.env` files need the rename applied (`WALLET_RIAL` →
|
|
||||||
`WALLET_RIAL_DEPOSIT`, `WALLET_REWARD` → `WALLET_USER_BILLBOARD_VISIT_INCOME`) plus this
|
|
||||||
new key added, or `main/settings.py` will fail at startup with
|
|
||||||
`decouple.UndefinedValueError`.
|
|
||||||
|
|
||||||
### 2.3 Per-recipient destination routing wired in
|
|
||||||
|
|
||||||
`Recipient.get_wallet_category_uuid()` existed already (`self.wallet_uuid or <fallback>`)
|
|
||||||
but nothing called it — every payout hardcoded `WALLET_REWARD` as `payee_wallet`
|
|
||||||
regardless of what a `Recipient` might specify. It's now the actual source of
|
|
||||||
`payee_wallet` in `Promotion.promote()`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
payee_wallet = self.recipient.get_wallet_category_uuid()
|
|
||||||
```
|
|
||||||
|
|
||||||
So a `Recipient` with its own `wallet_uuid` set now routes its payout to that wallet type
|
|
||||||
instead of the default; a `Recipient` with `wallet_uuid=None` falls back to
|
|
||||||
`WALLET_USER_BILLBOARD_VISIT_INCOME`. This mirrors `EscrowWalletPayment.destination_wallet`
|
|
||||||
in `advertising` — a per-row field read at call time instead of one hardcoded constant —
|
|
||||||
without inventing a new abstraction: `Recipient.wallet_uuid` and
|
|
||||||
`get_wallet_category_uuid()` already existed in this codebase, they just weren't
|
|
||||||
connected to anything.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Before / after, side by side
|
|
||||||
|
|
||||||
### `main/settings.py`
|
|
||||||
|
|
||||||
```diff
|
|
||||||
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
-WALLET_RIAL = config('WALLET_RIAL', cast=str)
|
|
||||||
-WALLET_REWARD = config('WALLET_REWARD', cast=str)
|
|
||||||
+WALLET_RIAL_DEPOSIT = config('WALLET_RIAL_DEPOSIT', cast=str)
|
|
||||||
+WALLET_USER_BILLBOARD_VISIT_INCOME = config('WALLET_USER_BILLBOARD_VISIT_INCOME', cast=str)
|
|
||||||
+# Company-side pool promotion payouts are drawn from; must be distinct from the user-side
|
|
||||||
+# wallet above. TODO(you): replace with the real wallet-type UUID from the wallet service.
|
|
||||||
+WALLET_PROMOTIONS_CREDIT = config('WALLET_PROMOTIONS_CREDIT', cast=str)
|
|
||||||
```
|
|
||||||
|
|
||||||
### `apps/promotions/models.py` — `Recipient.get_wallet_category_uuid()`
|
|
||||||
|
|
||||||
```diff
|
|
||||||
def get_wallet_category_uuid(self):
|
|
||||||
- return self.wallet_uuid or settings.WALLET_PROMOTION_CATEGORY_UUID
|
|
||||||
+ return self.wallet_uuid or settings.WALLET_USER_BILLBOARD_VISIT_INCOME
|
|
||||||
```
|
|
||||||
|
|
||||||
### `apps/promotions/models.py` — `Promotion.promote()`
|
|
||||||
|
|
||||||
```diff
|
|
||||||
payment_uuid = str(self.uuid)
|
|
||||||
|
|
||||||
+ payee_wallet = self.recipient.get_wallet_category_uuid()
|
|
||||||
+
|
|
||||||
data = {
|
|
||||||
"uuid": payment_uuid,
|
|
||||||
"payee": str(self.user_uuid),
|
|
||||||
"payee_type": 1,
|
|
||||||
- "payee_wallet": settings.WALLET_REWARD,
|
|
||||||
+ "payee_wallet": payee_wallet,
|
|
||||||
"amount": self.promotion_amount,
|
|
||||||
"details": {
|
|
||||||
'description': str(_(self.recipient.label)),
|
|
||||||
'reference_id': str(self.pk),
|
|
||||||
'application_details_url': ''
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
- submit_response = deposit_to_user_wallet_submit(settings.WALLET_REWARD, data)
|
|
||||||
+ submit_response = deposit_to_user_wallet_submit(settings.WALLET_PROMOTIONS_CREDIT, data)
|
|
||||||
...
|
|
||||||
try:
|
|
||||||
- verify_response = deposit_to_user_wallet_verify(settings.WALLET_REWARD, submit_response.uuid)
|
|
||||||
+ verify_response = deposit_to_user_wallet_verify(settings.WALLET_PROMOTIONS_CREDIT, submit_response.uuid)
|
|
||||||
```
|
|
||||||
|
|
||||||
### What the deposit call looks like now, end to end
|
|
||||||
|
|
||||||
| | Before | After |
|
|
||||||
|---|---|---|
|
|
||||||
| `payer_wallet` (call-target, company money source) | `WALLET_REWARD` | `WALLET_PROMOTIONS_CREDIT` |
|
|
||||||
| `payee_wallet` (body, user-side credit type) | `WALLET_REWARD` (same UUID as source) | `Recipient.wallet_uuid`, falling back to `WALLET_USER_BILLBOARD_VISIT_INCOME` |
|
|
||||||
| Per-recipient routing | Not possible — one hardcoded constant | Possible — set `Recipient.wallet_uuid` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Files touched
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
|---|---|
|
|
||||||
| `main/settings.py` | Renamed `WALLET_RIAL`→`WALLET_RIAL_DEPOSIT`, `WALLET_REWARD`→`WALLET_USER_BILLBOARD_VISIT_INCOME`; added `WALLET_PROMOTIONS_CREDIT`. |
|
|
||||||
| `apps/promotions/models.py` | `Recipient.get_wallet_category_uuid()` fallback fixed to point at a setting that actually exists; `Promotion.promote()` now uses `WALLET_PROMOTIONS_CREDIT` as `payer_wallet` and `recipient.get_wallet_category_uuid()` as `payee_wallet`. |
|
|
||||||
| `.env.example` | New — didn't exist before. Documents every `config()` var read by `main/settings.py`, including the new wallet keys as placeholders. |
|
|
||||||
| `README.md` | Config reference table updated to the renamed/new settings. |
|
|
||||||
|
|
||||||
Not touched: `apps/promotions/tests.py` — its wallet mocks patch the client functions
|
|
||||||
directly (`patch('apps.promotions.models.deposit_to_user_wallet_submit', ...)`) rather
|
|
||||||
than asserting on which UUID was passed, so they don't need updating for this change, but
|
|
||||||
they also don't exercise the routing fix — there's no test asserting `payer_wallet` /
|
|
||||||
`payee_wallet` on the call. `apps/promotions/handlers.py` (the dead processor/handler
|
|
||||||
scaffolding noted in the README's watch list) — unrelated, left as-is.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. What you need to do before this runs anywhere
|
|
||||||
|
|
||||||
1. Get a real wallet-type UUID for `WALLET_PROMOTIONS_CREDIT` from the wallet-service
|
|
||||||
team — it must be a genuine, dedicated pool, not a reused existing UUID (that's the
|
|
||||||
exact bug this change fixes).
|
|
||||||
2. In every environment's `.env` (local, staging, prod — none are checked into this repo):
|
|
||||||
- Rename `WALLET_RIAL` → `WALLET_RIAL_DEPOSIT` (same value, key renamed).
|
|
||||||
- Rename `WALLET_REWARD` → `WALLET_USER_BILLBOARD_VISIT_INCOME` (same value, key renamed).
|
|
||||||
- Add `WALLET_PROMOTIONS_CREDIT` with the real UUID from step 1.
|
|
||||||
3. Until step 2 is done in a given environment, `main/settings.py` will fail to import
|
|
||||||
with `decouple.UndefinedValueError: WALLET_RIAL_DEPOSIT not found` — the service won't
|
|
||||||
start at all, not just fail at payout time. Treat this as a deploy-blocking config
|
|
||||||
change, not a code-only one.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Follow-up: explicit per-recipient wallet destination (2026-08-25)
|
|
||||||
|
|
||||||
Not every payout is a cash-like user reward. Some fund billboard/ad credit instead — money
|
|
||||||
that should land in the **advertising** service's own transit wallet, not the user's
|
|
||||||
personal reward wallet.
|
|
||||||
|
|
||||||
An earlier version of this follow-up tried to infer the destination from
|
|
||||||
`PromotionTypeChoices` (`first_ad_view` / `capture` / `first_ad_create`), a promotion
|
|
||||||
*category* field that existed but had never been given real values. That was reverted: it
|
|
||||||
buried a wallet-routing decision inside a general-purpose categorization field, coupling
|
|
||||||
two things that should vary independently — a promotion's category and where its money
|
|
||||||
goes are not the same fact, and the next new promotion type would need someone to remember
|
|
||||||
to also classify it for wallet purposes.
|
|
||||||
|
|
||||||
Instead, `Recipient` gets a field that says the routing decision directly:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class WalletDestinationChoices(models.TextChoices):
|
|
||||||
USER_REWARD = 'user_reward', _('user reward wallet')
|
|
||||||
ADVERTISING_TRANSIT = 'advertising_transit', _('advertising transit wallet')
|
|
||||||
```
|
|
||||||
|
|
||||||
| `wallet_destination` | Nature | Resolves to |
|
|
||||||
|---|---|---|
|
|
||||||
| `user_reward` (default) | Cash-like user reward | `WALLET_USER_BILLBOARD_VISIT_INCOME` |
|
|
||||||
| `advertising_transit` | Billboard/ad credit | `WALLET_ADVERTISING_TRANSIT` (advertising's own company pool) |
|
|
||||||
|
|
||||||
`PromotionTypeChoices` is left as it was before any of this — an empty enum, unused. It's
|
|
||||||
not part of this decision.
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
- `main/settings.py` — new `WALLET_ADVERTISING_TRANSIT` setting. Same wallet-service UUID
|
|
||||||
as advertising's own setting of the same name — copy that repo's real value in, don't
|
|
||||||
provision a second UUID for the same wallet.
|
|
||||||
- `apps/promotions/models.py`:
|
|
||||||
- New `WalletDestinationChoices` enum, next to the existing `RecipientTypeChoices`.
|
|
||||||
- New `Recipient.wallet_destination` field (`CharField`, `db_index=True`, default
|
|
||||||
`USER_REWARD`) — a real DB column, unlike the earlier `promotion_type` attempt which
|
|
||||||
only changed field-level `choices=` metadata.
|
|
||||||
- `Recipient.get_wallet_category_uuid()`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_wallet_category_uuid(self):
|
|
||||||
if self.wallet_uuid:
|
|
||||||
return self.wallet_uuid
|
|
||||||
|
|
||||||
if self.wallet_destination == WalletDestinationChoices.ADVERTISING_TRANSIT:
|
|
||||||
return settings.WALLET_ADVERTISING_TRANSIT
|
|
||||||
|
|
||||||
return settings.WALLET_USER_BILLBOARD_VISIT_INCOME
|
|
||||||
```
|
|
||||||
|
|
||||||
Priority order: an explicit `Recipient.wallet_uuid` always wins (the per-recipient raw
|
|
||||||
override from the original refactor above); otherwise `wallet_destination` picks the
|
|
||||||
wallet type; `user_reward` is the default so existing rows behave exactly as before
|
|
||||||
this change until someone opts them into `advertising_transit`.
|
|
||||||
- `apps/promotions/migrations/0010_recipient_wallet_destination.py` — new migration adding
|
|
||||||
the column, `AddField` with `default='user_reward'` so existing rows backfill safely.
|
|
||||||
- `apps/promotions/tests.py` — the `first-ad-create` fixture now sets
|
|
||||||
`wallet_destination=WalletDestinationChoices.ADVERTISING_TRANSIT`;
|
|
||||||
`test_first_ad_create_event_status_processed` asserts the deposit call actually receives
|
|
||||||
`WALLET_PROMOTIONS_CREDIT` as `payer_wallet` and `WALLET_ADVERTISING_TRANSIT` as
|
|
||||||
`payee_wallet`; `test_get_wallet_category_uuid_routing` unit-tests all four routing cases
|
|
||||||
directly against `Recipient.get_wallet_category_uuid()`.
|
|
||||||
- `README.md` — config reference table and the playbook's step 3 example updated to show
|
|
||||||
setting `wallet_destination` on a new `Recipient`.
|
|
||||||
|
|
||||||
### What's still manual
|
|
||||||
|
|
||||||
- Existing `Plan`/`Recipient` rows in a live database default to `wallet_destination='user_reward'`
|
|
||||||
on migrate — behavior for them doesn't change. Whoever owns the real `capture` /
|
|
||||||
`first-ad-create` plans needs to explicitly set `wallet_destination='advertising_transit'`
|
|
||||||
on their `Recipient` rows (via admin or a follow-up data migration) for those specific
|
|
||||||
payouts to actually route to the advertising transit wallet.
|
|
||||||
- `WALLET_ADVERTISING_TRANSIT` is a second **required** env var on top of
|
|
||||||
`WALLET_PROMOTIONS_CREDIT` — same deploy-blocking caveat as §5: missing it fails
|
|
||||||
`main/settings.py` import, not just a payout at runtime.
|
|
||||||
- This migration hasn't been run against a real database in this environment (no local
|
|
||||||
`.env`/DB configured here) — run `manage.py migrate` and confirm `0010` applies cleanly
|
|
||||||
before deploying.
|
|
||||||
0
gooyal_wallet_client/api/wallet/__init__.py
Normal file
0
gooyal_wallet_client/api/wallet/__init__.py
Normal file
|
|
@ -5,22 +5,24 @@ import httpx
|
||||||
|
|
||||||
from ... import errors
|
from ... import errors
|
||||||
from ...client import AuthenticatedClient, Client
|
from ...client import AuthenticatedClient, Client
|
||||||
from ...models.account import Account
|
from ...models.application_balance import ApplicationBalance
|
||||||
from ...types import Response
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs() -> dict[str, Any]:
|
def _get_kwargs() -> dict[str, Any]:
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": "/users/api/account/",
|
"url": "/wallet/api/application/balance",
|
||||||
}
|
}
|
||||||
|
|
||||||
return _kwargs
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Account]:
|
def _parse_response(
|
||||||
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
|
) -> Optional[ApplicationBalance]:
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_200 = Account.from_dict(response.json())
|
response_200 = ApplicationBalance.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
|
|
@ -29,7 +31,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Account]:
|
def _build_response(
|
||||||
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
|
) -> Response[ApplicationBalance]:
|
||||||
return Response(
|
return Response(
|
||||||
status_code=HTTPStatus(response.status_code),
|
status_code=HTTPStatus(response.status_code),
|
||||||
content=response.content,
|
content=response.content,
|
||||||
|
|
@ -41,14 +45,15 @@ def _build_response(*, client: Union[AuthenticatedClient, Client], response: htt
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[Account]:
|
) -> Response[ApplicationBalance]:
|
||||||
"""
|
"""API for getting specified user or application balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[Account]
|
Response[ApplicationBalance]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs()
|
kwargs = _get_kwargs()
|
||||||
|
|
@ -63,14 +68,15 @@ def sync_detailed(
|
||||||
def sync(
|
def sync(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[Account]:
|
) -> Optional[ApplicationBalance]:
|
||||||
"""
|
"""API for getting specified user or application balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Account
|
ApplicationBalance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
|
|
@ -81,14 +87,15 @@ def sync(
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[Account]:
|
) -> Response[ApplicationBalance]:
|
||||||
"""
|
"""API for getting specified user or application balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[Account]
|
Response[ApplicationBalance]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs()
|
kwargs = _get_kwargs()
|
||||||
|
|
@ -101,14 +108,15 @@ async def asyncio_detailed(
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[Account]:
|
) -> Optional[ApplicationBalance]:
|
||||||
"""
|
"""API for getting specified user or application balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Account
|
ApplicationBalance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -10,7 +10,6 @@ from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
def _get_kwargs(
|
||||||
payer_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
body: ApplicationDeposit,
|
body: ApplicationDeposit,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
@ -18,11 +17,12 @@ def _get_kwargs(
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "post",
|
"method": "post",
|
||||||
"url": f"/wallet/api/application/{payer_wallet}/deposit/",
|
"url": "/wallet/api/application/deposit/",
|
||||||
}
|
}
|
||||||
|
|
||||||
_kwargs["json"] = body.to_dict()
|
_body = body.to_dict()
|
||||||
|
|
||||||
|
_kwargs["json"] = _body
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
_kwargs["headers"] = headers
|
||||||
|
|
@ -36,7 +36,6 @@ def _parse_response(
|
||||||
response_201 = ApplicationDeposit.from_dict(response.json())
|
response_201 = ApplicationDeposit.from_dict(response.json())
|
||||||
|
|
||||||
return response_201
|
return response_201
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
else:
|
else:
|
||||||
|
|
@ -55,14 +54,12 @@ def _build_response(
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
payer_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationDeposit,
|
body: ApplicationDeposit,
|
||||||
) -> Response[ApplicationDeposit]:
|
) -> Response[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
body (ApplicationDeposit):
|
body (ApplicationDeposit):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -74,7 +71,6 @@ def sync_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -86,14 +82,12 @@ def sync_detailed(
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
def sync(
|
||||||
payer_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationDeposit,
|
body: ApplicationDeposit,
|
||||||
) -> Optional[ApplicationDeposit]:
|
) -> Optional[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
body (ApplicationDeposit):
|
body (ApplicationDeposit):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -105,21 +99,18 @@ def sync(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
client=client,
|
client=client,
|
||||||
body=body,
|
body=body,
|
||||||
).parsed
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
payer_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationDeposit,
|
body: ApplicationDeposit,
|
||||||
) -> Response[ApplicationDeposit]:
|
) -> Response[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
body (ApplicationDeposit):
|
body (ApplicationDeposit):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -131,7 +122,6 @@ async def asyncio_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -141,14 +131,12 @@ async def asyncio_detailed(
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
payer_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationDeposit,
|
body: ApplicationDeposit,
|
||||||
) -> Optional[ApplicationDeposit]:
|
) -> Optional[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
body (ApplicationDeposit):
|
body (ApplicationDeposit):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -161,7 +149,6 @@ async def asyncio(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
await asyncio_detailed(
|
await asyncio_detailed(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
client=client,
|
client=client,
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
@ -10,12 +10,11 @@ from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
def _get_kwargs(
|
||||||
payer_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": f"/wallet/api/application/{payer_wallet}/deposit/{uuid}/verify",
|
"url": f"/wallet/api/application/deposit/{uuid}/verify",
|
||||||
}
|
}
|
||||||
|
|
||||||
return _kwargs
|
return _kwargs
|
||||||
|
|
@ -28,7 +27,6 @@ def _parse_response(
|
||||||
response_200 = ApplicationDeposit.from_dict(response.json())
|
response_200 = ApplicationDeposit.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
else:
|
else:
|
||||||
|
|
@ -47,14 +45,12 @@ def _build_response(
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
payer_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[ApplicationDeposit]:
|
) -> Response[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -66,7 +62,6 @@ def sync_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -78,14 +73,12 @@ def sync_detailed(
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
def sync(
|
||||||
payer_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[ApplicationDeposit]:
|
) -> Optional[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -97,21 +90,18 @@ def sync(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
client=client,
|
client=client,
|
||||||
).parsed
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
payer_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[ApplicationDeposit]:
|
) -> Response[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -123,7 +113,6 @@ async def asyncio_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -133,14 +122,12 @@ async def asyncio_detailed(
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
payer_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[ApplicationDeposit]:
|
) -> Optional[ApplicationDeposit]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payer_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -153,7 +140,6 @@ async def asyncio(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
await asyncio_detailed(
|
await asyncio_detailed(
|
||||||
payer_wallet=payer_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
client=client,
|
client=client,
|
||||||
)
|
)
|
||||||
|
|
@ -5,7 +5,7 @@ import httpx
|
||||||
|
|
||||||
from ... import errors
|
from ... import errors
|
||||||
from ...client import AuthenticatedClient, Client
|
from ...client import AuthenticatedClient, Client
|
||||||
from ...models.paginated_public_user_list import PaginatedPublicUserList
|
from ...models.paginated_application_transaction_list import PaginatedApplicationTransactionList
|
||||||
from ...types import UNSET, Response, Unset
|
from ...types import UNSET, Response, Unset
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ def _get_kwargs(
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": "/users/api/users/",
|
"url": "/wallet/api/application/",
|
||||||
"params": params,
|
"params": params,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,9 +30,9 @@ def _get_kwargs(
|
||||||
|
|
||||||
def _parse_response(
|
def _parse_response(
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
) -> Optional[PaginatedPublicUserList]:
|
) -> Optional[PaginatedApplicationTransactionList]:
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_200 = PaginatedPublicUserList.from_dict(response.json())
|
response_200 = PaginatedApplicationTransactionList.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
|
|
@ -43,7 +43,7 @@ def _parse_response(
|
||||||
|
|
||||||
def _build_response(
|
def _build_response(
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
) -> Response[PaginatedPublicUserList]:
|
) -> Response[PaginatedApplicationTransactionList]:
|
||||||
return Response(
|
return Response(
|
||||||
status_code=HTTPStatus(response.status_code),
|
status_code=HTTPStatus(response.status_code),
|
||||||
content=response.content,
|
content=response.content,
|
||||||
|
|
@ -56,7 +56,7 @@ def sync_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Response[PaginatedPublicUserList]:
|
) -> Response[PaginatedApplicationTransactionList]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
@ -66,7 +66,7 @@ def sync_detailed(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PaginatedPublicUserList]
|
Response[PaginatedApplicationTransactionList]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
|
|
@ -84,7 +84,7 @@ def sync(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Optional[PaginatedPublicUserList]:
|
) -> Optional[PaginatedApplicationTransactionList]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
@ -94,7 +94,7 @@ def sync(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PaginatedPublicUserList
|
PaginatedApplicationTransactionList
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
|
|
@ -107,7 +107,7 @@ async def asyncio_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Response[PaginatedPublicUserList]:
|
) -> Response[PaginatedApplicationTransactionList]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
@ -117,7 +117,7 @@ async def asyncio_detailed(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PaginatedPublicUserList]
|
Response[PaginatedApplicationTransactionList]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
|
|
@ -133,7 +133,7 @@ async def asyncio(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Optional[PaginatedPublicUserList]:
|
) -> Optional[PaginatedApplicationTransactionList]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
@ -143,7 +143,7 @@ async def asyncio(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PaginatedPublicUserList
|
PaginatedApplicationTransactionList
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -1,29 +1,30 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ... import errors
|
from ... import errors
|
||||||
from ...client import AuthenticatedClient, Client
|
from ...client import AuthenticatedClient, Client
|
||||||
from ...models.public_user import PublicUser
|
from ...models.application_transaction import ApplicationTransaction
|
||||||
from ...types import Response
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
def _get_kwargs(
|
||||||
uuid: UUID,
|
uuid: str,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": f"/users/api/users/{uuid}/",
|
"url": f"/wallet/api/application/{uuid}/",
|
||||||
}
|
}
|
||||||
|
|
||||||
return _kwargs
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[PublicUser]:
|
def _parse_response(
|
||||||
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
|
) -> Optional[ApplicationTransaction]:
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_200 = PublicUser.from_dict(response.json())
|
response_200 = ApplicationTransaction.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
|
|
@ -32,7 +33,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[PublicUser]:
|
def _build_response(
|
||||||
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
|
) -> Response[ApplicationTransaction]:
|
||||||
return Response(
|
return Response(
|
||||||
status_code=HTTPStatus(response.status_code),
|
status_code=HTTPStatus(response.status_code),
|
||||||
content=response.content,
|
content=response.content,
|
||||||
|
|
@ -42,20 +45,20 @@ def _build_response(*, client: Union[AuthenticatedClient, Client], response: htt
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
uuid: UUID,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[PublicUser]:
|
) -> Response[ApplicationTransaction]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
uuid (UUID):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PublicUser]
|
Response[ApplicationTransaction]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
|
|
@ -70,20 +73,20 @@ def sync_detailed(
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
def sync(
|
||||||
uuid: UUID,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[PublicUser]:
|
) -> Optional[ApplicationTransaction]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
uuid (UUID):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PublicUser
|
ApplicationTransaction
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
|
|
@ -93,20 +96,20 @@ def sync(
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
uuid: UUID,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[PublicUser]:
|
) -> Response[ApplicationTransaction]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
uuid (UUID):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PublicUser]
|
Response[ApplicationTransaction]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
|
|
@ -119,20 +122,20 @@ async def asyncio_detailed(
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
uuid: UUID,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[PublicUser]:
|
) -> Optional[ApplicationTransaction]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
uuid (UUID):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PublicUser
|
ApplicationTransaction
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -10,7 +10,6 @@ from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
def _get_kwargs(
|
||||||
payee_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
body: ApplicationWithdraw,
|
body: ApplicationWithdraw,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|
@ -18,11 +17,12 @@ def _get_kwargs(
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "post",
|
"method": "post",
|
||||||
"url": f"/wallet/api/application/{payee_wallet}/withdraw/",
|
"url": "/wallet/api/application/withdraw/",
|
||||||
}
|
}
|
||||||
|
|
||||||
_kwargs["json"] = body.to_dict()
|
_body = body.to_dict()
|
||||||
|
|
||||||
|
_kwargs["json"] = _body
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
_kwargs["headers"] = headers
|
||||||
|
|
@ -36,7 +36,6 @@ def _parse_response(
|
||||||
response_201 = ApplicationWithdraw.from_dict(response.json())
|
response_201 = ApplicationWithdraw.from_dict(response.json())
|
||||||
|
|
||||||
return response_201
|
return response_201
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
else:
|
else:
|
||||||
|
|
@ -55,14 +54,12 @@ def _build_response(
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
payee_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationWithdraw,
|
body: ApplicationWithdraw,
|
||||||
) -> Response[ApplicationWithdraw]:
|
) -> Response[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
body (ApplicationWithdraw):
|
body (ApplicationWithdraw):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -74,7 +71,6 @@ def sync_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -86,14 +82,12 @@ def sync_detailed(
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
def sync(
|
||||||
payee_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationWithdraw,
|
body: ApplicationWithdraw,
|
||||||
) -> Optional[ApplicationWithdraw]:
|
) -> Optional[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
body (ApplicationWithdraw):
|
body (ApplicationWithdraw):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -105,21 +99,18 @@ def sync(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
client=client,
|
client=client,
|
||||||
body=body,
|
body=body,
|
||||||
).parsed
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
payee_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationWithdraw,
|
body: ApplicationWithdraw,
|
||||||
) -> Response[ApplicationWithdraw]:
|
) -> Response[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
body (ApplicationWithdraw):
|
body (ApplicationWithdraw):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -131,7 +122,6 @@ async def asyncio_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -141,14 +131,12 @@ async def asyncio_detailed(
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
payee_wallet: str,
|
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
body: ApplicationWithdraw,
|
body: ApplicationWithdraw,
|
||||||
) -> Optional[ApplicationWithdraw]:
|
) -> Optional[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
body (ApplicationWithdraw):
|
body (ApplicationWithdraw):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -161,7 +149,6 @@ async def asyncio(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
await asyncio_detailed(
|
await asyncio_detailed(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
client=client,
|
client=client,
|
||||||
body=body,
|
body=body,
|
||||||
)
|
)
|
||||||
|
|
@ -10,12 +10,11 @@ from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
def _get_kwargs(
|
||||||
payee_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": f"/wallet/api/application/{payee_wallet}/withdraw/{uuid}/verify",
|
"url": f"/wallet/api/application/withdraw/{uuid}/verify",
|
||||||
}
|
}
|
||||||
|
|
||||||
return _kwargs
|
return _kwargs
|
||||||
|
|
@ -28,7 +27,6 @@ def _parse_response(
|
||||||
response_200 = ApplicationWithdraw.from_dict(response.json())
|
response_200 = ApplicationWithdraw.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
else:
|
else:
|
||||||
|
|
@ -47,14 +45,12 @@ def _build_response(
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
payee_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[ApplicationWithdraw]:
|
) -> Response[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -66,7 +62,6 @@ def sync_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -78,14 +73,12 @@ def sync_detailed(
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
def sync(
|
||||||
payee_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[ApplicationWithdraw]:
|
) -> Optional[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -97,21 +90,18 @@ def sync(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
client=client,
|
client=client,
|
||||||
).parsed
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
payee_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[ApplicationWithdraw]:
|
) -> Response[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -123,7 +113,6 @@ async def asyncio_detailed(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -133,14 +122,12 @@ async def asyncio_detailed(
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
payee_wallet: str,
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[ApplicationWithdraw]:
|
) -> Optional[ApplicationWithdraw]:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
payee_wallet (str):
|
|
||||||
uuid (str):
|
uuid (str):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -153,7 +140,6 @@ async def asyncio(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
await asyncio_detailed(
|
await asyncio_detailed(
|
||||||
payee_wallet=payee_wallet,
|
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
client=client,
|
client=client,
|
||||||
)
|
)
|
||||||
|
|
@ -5,7 +5,7 @@ import httpx
|
||||||
|
|
||||||
from ... import errors
|
from ... import errors
|
||||||
from ...client import AuthenticatedClient, Client
|
from ...client import AuthenticatedClient, Client
|
||||||
from ...models.paginated_application_list import PaginatedApplicationList
|
from ...models.paginated_user_transaction_list import PaginatedUserTransactionList
|
||||||
from ...types import UNSET, Response, Unset
|
from ...types import UNSET, Response, Unset
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ def _get_kwargs(
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": "/oauth2/apis/applications/",
|
"url": "/wallet/api/transactions/",
|
||||||
"params": params,
|
"params": params,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,9 +30,9 @@ def _get_kwargs(
|
||||||
|
|
||||||
def _parse_response(
|
def _parse_response(
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
) -> Optional[PaginatedApplicationList]:
|
) -> Optional[PaginatedUserTransactionList]:
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_200 = PaginatedApplicationList.from_dict(response.json())
|
response_200 = PaginatedUserTransactionList.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
|
|
@ -43,7 +43,7 @@ def _parse_response(
|
||||||
|
|
||||||
def _build_response(
|
def _build_response(
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
) -> Response[PaginatedApplicationList]:
|
) -> Response[PaginatedUserTransactionList]:
|
||||||
return Response(
|
return Response(
|
||||||
status_code=HTTPStatus(response.status_code),
|
status_code=HTTPStatus(response.status_code),
|
||||||
content=response.content,
|
content=response.content,
|
||||||
|
|
@ -56,8 +56,13 @@ def sync_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Response[PaginatedApplicationList]:
|
) -> Response[PaginatedUserTransactionList]:
|
||||||
"""
|
"""GET: API for getting list of transactions for authenticated user
|
||||||
|
POST: API for creating transaction for transferring funds from authenticated user to application
|
||||||
|
owner. (deprecated)
|
||||||
|
args:
|
||||||
|
- user: authenticated user
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
||||||
|
|
@ -66,7 +71,7 @@ def sync_detailed(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PaginatedApplicationList]
|
Response[PaginatedUserTransactionList]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
|
|
@ -84,8 +89,13 @@ def sync(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Optional[PaginatedApplicationList]:
|
) -> Optional[PaginatedUserTransactionList]:
|
||||||
"""
|
"""GET: API for getting list of transactions for authenticated user
|
||||||
|
POST: API for creating transaction for transferring funds from authenticated user to application
|
||||||
|
owner. (deprecated)
|
||||||
|
args:
|
||||||
|
- user: authenticated user
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
||||||
|
|
@ -94,7 +104,7 @@ def sync(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PaginatedApplicationList
|
PaginatedUserTransactionList
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
|
|
@ -107,8 +117,13 @@ async def asyncio_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Response[PaginatedApplicationList]:
|
) -> Response[PaginatedUserTransactionList]:
|
||||||
"""
|
"""GET: API for getting list of transactions for authenticated user
|
||||||
|
POST: API for creating transaction for transferring funds from authenticated user to application
|
||||||
|
owner. (deprecated)
|
||||||
|
args:
|
||||||
|
- user: authenticated user
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
||||||
|
|
@ -117,7 +132,7 @@ async def asyncio_detailed(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PaginatedApplicationList]
|
Response[PaginatedUserTransactionList]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
kwargs = _get_kwargs(
|
||||||
|
|
@ -133,8 +148,13 @@ async def asyncio(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
page: Union[Unset, int] = UNSET,
|
page: Union[Unset, int] = UNSET,
|
||||||
) -> Optional[PaginatedApplicationList]:
|
) -> Optional[PaginatedUserTransactionList]:
|
||||||
"""
|
"""GET: API for getting list of transactions for authenticated user
|
||||||
|
POST: API for creating transaction for transferring funds from authenticated user to application
|
||||||
|
owner. (deprecated)
|
||||||
|
args:
|
||||||
|
- user: authenticated user
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
page (Union[Unset, int]):
|
page (Union[Unset, int]):
|
||||||
|
|
||||||
|
|
@ -143,7 +163,7 @@ async def asyncio(
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PaginatedApplicationList
|
PaginatedUserTransactionList
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -5,32 +5,31 @@ import httpx
|
||||||
|
|
||||||
from ... import errors
|
from ... import errors
|
||||||
from ...client import AuthenticatedClient, Client
|
from ...client import AuthenticatedClient, Client
|
||||||
from ...models.push_user import PushUser
|
from ...models.my_wallet import MyWallet
|
||||||
from ...types import Response
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs() -> dict[str, Any]:
|
def _get_kwargs() -> dict[str, Any]:
|
||||||
_kwargs: dict[str, Any] = {
|
_kwargs: dict[str, Any] = {
|
||||||
"method": "get",
|
"method": "get",
|
||||||
"url": "/push/user/push_user/",
|
"url": "/wallet/api/wallet/balance",
|
||||||
}
|
}
|
||||||
|
|
||||||
return _kwargs
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[PushUser]:
|
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[MyWallet]:
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
response_200 = PushUser.from_dict(response.json())
|
response_200 = MyWallet.from_dict(response.json())
|
||||||
|
|
||||||
return response_200
|
return response_200
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
if client.raise_on_unexpected_status:
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[PushUser]:
|
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[MyWallet]:
|
||||||
return Response(
|
return Response(
|
||||||
status_code=HTTPStatus(response.status_code),
|
status_code=HTTPStatus(response.status_code),
|
||||||
content=response.content,
|
content=response.content,
|
||||||
|
|
@ -42,14 +41,15 @@ def _build_response(*, client: Union[AuthenticatedClient, Client], response: htt
|
||||||
def sync_detailed(
|
def sync_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[PushUser]:
|
) -> Response[MyWallet]:
|
||||||
"""
|
"""API for getting authenticated user balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PushUser]
|
Response[MyWallet]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs()
|
kwargs = _get_kwargs()
|
||||||
|
|
@ -64,14 +64,15 @@ def sync_detailed(
|
||||||
def sync(
|
def sync(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[PushUser]:
|
) -> Optional[MyWallet]:
|
||||||
"""
|
"""API for getting authenticated user balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PushUser
|
MyWallet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return sync_detailed(
|
return sync_detailed(
|
||||||
|
|
@ -82,14 +83,15 @@ def sync(
|
||||||
async def asyncio_detailed(
|
async def asyncio_detailed(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Response[PushUser]:
|
) -> Response[MyWallet]:
|
||||||
"""
|
"""API for getting authenticated user balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response[PushUser]
|
Response[MyWallet]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs = _get_kwargs()
|
kwargs = _get_kwargs()
|
||||||
|
|
@ -102,14 +104,15 @@ async def asyncio_detailed(
|
||||||
async def asyncio(
|
async def asyncio(
|
||||||
*,
|
*,
|
||||||
client: AuthenticatedClient,
|
client: AuthenticatedClient,
|
||||||
) -> Optional[PushUser]:
|
) -> Optional[MyWallet]:
|
||||||
"""
|
"""API for getting authenticated user balance.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
PushUser
|
MyWallet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (
|
return (
|
||||||
27
gooyal_wallet_client/models/__init__.py
Normal file
27
gooyal_wallet_client/models/__init__.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
"""Contains all the data models used in inputs/outputs"""
|
||||||
|
|
||||||
|
from .application_balance import ApplicationBalance
|
||||||
|
from .application_deposit import ApplicationDeposit
|
||||||
|
from .application_transaction import ApplicationTransaction
|
||||||
|
from .application_withdraw import ApplicationWithdraw
|
||||||
|
from .my_wallet import MyWallet
|
||||||
|
from .paginated_application_transaction_list import PaginatedApplicationTransactionList
|
||||||
|
from .paginated_user_transaction_list import PaginatedUserTransactionList
|
||||||
|
from .payee_type_enum import PayeeTypeEnum
|
||||||
|
from .payer_type_enum import PayerTypeEnum
|
||||||
|
from .state_enum import StateEnum
|
||||||
|
from .user_transaction import UserTransaction
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"ApplicationBalance",
|
||||||
|
"ApplicationDeposit",
|
||||||
|
"ApplicationTransaction",
|
||||||
|
"ApplicationWithdraw",
|
||||||
|
"MyWallet",
|
||||||
|
"PaginatedApplicationTransactionList",
|
||||||
|
"PaginatedUserTransactionList",
|
||||||
|
"PayeeTypeEnum",
|
||||||
|
"PayerTypeEnum",
|
||||||
|
"StateEnum",
|
||||||
|
"UserTransaction",
|
||||||
|
)
|
||||||
|
|
@ -1,55 +1,45 @@
|
||||||
from collections.abc import Mapping
|
from typing import Any, TypeVar, Union
|
||||||
from typing import Any, TypeVar
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
from attrs import field as _attrs_field
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
T = TypeVar("T", bound="UserInquiry")
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ApplicationBalance")
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
@_attrs_define
|
||||||
class UserInquiry:
|
class ApplicationBalance:
|
||||||
"""
|
"""
|
||||||
Attributes:
|
Attributes:
|
||||||
uuid (UUID):
|
balance (Union[Unset, int]):
|
||||||
phone_number (str):
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
uuid: UUID
|
balance: Union[Unset, int] = UNSET
|
||||||
phone_number: str
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
uuid = str(self.uuid)
|
balance = self.balance
|
||||||
|
|
||||||
phone_number = self.phone_number
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
field_dict: dict[str, Any] = {}
|
||||||
field_dict.update(self.additional_properties)
|
field_dict.update(self.additional_properties)
|
||||||
field_dict.update(
|
field_dict.update({})
|
||||||
{
|
if balance is not UNSET:
|
||||||
"uuid": uuid,
|
field_dict["balance"] = balance
|
||||||
"phone_number": phone_number,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
uuid = UUID(d.pop("uuid"))
|
balance = d.pop("balance", UNSET)
|
||||||
|
|
||||||
phone_number = d.pop("phone_number")
|
application_balance = cls(
|
||||||
|
balance=balance,
|
||||||
user_inquiry = cls(
|
|
||||||
uuid=uuid,
|
|
||||||
phone_number=phone_number,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
user_inquiry.additional_properties = d
|
application_balance.additional_properties = d
|
||||||
return user_inquiry
|
return application_balance
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_keys(self) -> list[str]:
|
def additional_keys(self) -> list[str]:
|
||||||
235
gooyal_wallet_client/models/application_deposit.py
Normal file
235
gooyal_wallet_client/models/application_deposit.py
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
import datetime
|
||||||
|
from typing import Any, TypeVar, Union, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
from dateutil.parser import isoparse
|
||||||
|
|
||||||
|
from ..models.payee_type_enum import PayeeTypeEnum
|
||||||
|
from ..models.payer_type_enum import PayerTypeEnum
|
||||||
|
from ..models.state_enum import StateEnum
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ApplicationDeposit")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class ApplicationDeposit:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
uuid (UUID):
|
||||||
|
payer_id (Union[None, UUID]):
|
||||||
|
payer_type (Union[None, PayerTypeEnum]):
|
||||||
|
payee_id (UUID):
|
||||||
|
created_at (Union[None, datetime.datetime]):
|
||||||
|
state (StateEnum): * `1` - created
|
||||||
|
* `2` - delayed
|
||||||
|
* `3` - pending
|
||||||
|
* `4` - incomplete
|
||||||
|
* `5` - success
|
||||||
|
* `6` - failed
|
||||||
|
* `7` - expected_failure
|
||||||
|
amount (int):
|
||||||
|
updated_at (Union[None, datetime.datetime]):
|
||||||
|
payee_type (Union[None, PayeeTypeEnum, Unset]):
|
||||||
|
details (Union[Unset, Any]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid: UUID
|
||||||
|
payer_id: Union[None, UUID]
|
||||||
|
payer_type: Union[None, PayerTypeEnum]
|
||||||
|
payee_id: UUID
|
||||||
|
created_at: Union[None, datetime.datetime]
|
||||||
|
state: StateEnum
|
||||||
|
amount: int
|
||||||
|
updated_at: Union[None, datetime.datetime]
|
||||||
|
payee_type: Union[None, PayeeTypeEnum, Unset] = UNSET
|
||||||
|
details: Union[Unset, Any] = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
uuid = str(self.uuid)
|
||||||
|
|
||||||
|
payer_id: Union[None, str]
|
||||||
|
if isinstance(self.payer_id, UUID):
|
||||||
|
payer_id = str(self.payer_id)
|
||||||
|
else:
|
||||||
|
payer_id = self.payer_id
|
||||||
|
|
||||||
|
payer_type: Union[None, int]
|
||||||
|
if isinstance(self.payer_type, PayerTypeEnum):
|
||||||
|
payer_type = self.payer_type.value
|
||||||
|
else:
|
||||||
|
payer_type = self.payer_type
|
||||||
|
|
||||||
|
payee_id = str(self.payee_id)
|
||||||
|
|
||||||
|
created_at: Union[None, str]
|
||||||
|
if isinstance(self.created_at, datetime.datetime):
|
||||||
|
created_at = self.created_at.isoformat()
|
||||||
|
else:
|
||||||
|
created_at = self.created_at
|
||||||
|
|
||||||
|
state = self.state.value
|
||||||
|
|
||||||
|
amount = self.amount
|
||||||
|
|
||||||
|
updated_at: Union[None, str]
|
||||||
|
if isinstance(self.updated_at, datetime.datetime):
|
||||||
|
updated_at = self.updated_at.isoformat()
|
||||||
|
else:
|
||||||
|
updated_at = self.updated_at
|
||||||
|
|
||||||
|
payee_type: Union[None, Unset, int]
|
||||||
|
if isinstance(self.payee_type, Unset):
|
||||||
|
payee_type = UNSET
|
||||||
|
elif isinstance(self.payee_type, PayeeTypeEnum):
|
||||||
|
payee_type = self.payee_type.value
|
||||||
|
else:
|
||||||
|
payee_type = self.payee_type
|
||||||
|
|
||||||
|
details = self.details
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"uuid": uuid,
|
||||||
|
"payer_id": payer_id,
|
||||||
|
"payer_type": payer_type,
|
||||||
|
"payee_id": payee_id,
|
||||||
|
"created_at": created_at,
|
||||||
|
"state": state,
|
||||||
|
"amount": amount,
|
||||||
|
"updated_at": updated_at,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if payee_type is not UNSET:
|
||||||
|
field_dict["payee_type"] = payee_type
|
||||||
|
if details is not UNSET:
|
||||||
|
field_dict["details"] = details
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
|
def _parse_payer_id(data: object) -> Union[None, UUID]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
payer_id_type_0 = UUID(data)
|
||||||
|
|
||||||
|
return payer_id_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, UUID], data)
|
||||||
|
|
||||||
|
payer_id = _parse_payer_id(d.pop("payer_id"))
|
||||||
|
|
||||||
|
def _parse_payer_type(data: object) -> Union[None, PayerTypeEnum]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, int):
|
||||||
|
raise TypeError()
|
||||||
|
payer_type_type_0 = PayerTypeEnum(data)
|
||||||
|
|
||||||
|
return payer_type_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, PayerTypeEnum], data)
|
||||||
|
|
||||||
|
payer_type = _parse_payer_type(d.pop("payer_type"))
|
||||||
|
|
||||||
|
payee_id = UUID(d.pop("payee_id"))
|
||||||
|
|
||||||
|
def _parse_created_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
created_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return created_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
created_at = _parse_created_at(d.pop("created_at"))
|
||||||
|
|
||||||
|
state = StateEnum(d.pop("state"))
|
||||||
|
|
||||||
|
amount = d.pop("amount")
|
||||||
|
|
||||||
|
def _parse_updated_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
updated_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return updated_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
updated_at = _parse_updated_at(d.pop("updated_at"))
|
||||||
|
|
||||||
|
def _parse_payee_type(data: object) -> Union[None, PayeeTypeEnum, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, int):
|
||||||
|
raise TypeError()
|
||||||
|
payee_type_type_0 = PayeeTypeEnum(data)
|
||||||
|
|
||||||
|
return payee_type_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, PayeeTypeEnum, Unset], data)
|
||||||
|
|
||||||
|
payee_type = _parse_payee_type(d.pop("payee_type", UNSET))
|
||||||
|
|
||||||
|
details = d.pop("details", UNSET)
|
||||||
|
|
||||||
|
application_deposit = cls(
|
||||||
|
uuid=uuid,
|
||||||
|
payer_id=payer_id,
|
||||||
|
payer_type=payer_type,
|
||||||
|
payee_id=payee_id,
|
||||||
|
created_at=created_at,
|
||||||
|
state=state,
|
||||||
|
amount=amount,
|
||||||
|
updated_at=updated_at,
|
||||||
|
payee_type=payee_type,
|
||||||
|
details=details,
|
||||||
|
)
|
||||||
|
|
||||||
|
application_deposit.additional_properties = d
|
||||||
|
return application_deposit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
238
gooyal_wallet_client/models/application_transaction.py
Normal file
238
gooyal_wallet_client/models/application_transaction.py
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
import datetime
|
||||||
|
from typing import Any, TypeVar, Union, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
from dateutil.parser import isoparse
|
||||||
|
|
||||||
|
from ..models.payer_type_enum import PayerTypeEnum
|
||||||
|
from ..models.state_enum import StateEnum
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ApplicationTransaction")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class ApplicationTransaction:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
uuid (UUID):
|
||||||
|
payee_id (UUID):
|
||||||
|
payee_type (UUID):
|
||||||
|
created_at (Union[None, datetime.datetime]):
|
||||||
|
state (StateEnum): * `1` - created
|
||||||
|
* `2` - delayed
|
||||||
|
* `3` - pending
|
||||||
|
* `4` - incomplete
|
||||||
|
* `5` - success
|
||||||
|
* `6` - failed
|
||||||
|
* `7` - expected_failure
|
||||||
|
amount (int):
|
||||||
|
updated_at (Union[None, datetime.datetime]):
|
||||||
|
details (Any):
|
||||||
|
role (str):
|
||||||
|
payer_id (Union[None, UUID, Unset]):
|
||||||
|
payer_type (Union[None, PayerTypeEnum, Unset]):
|
||||||
|
delay (Union[Unset, int]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid: UUID
|
||||||
|
payee_id: UUID
|
||||||
|
payee_type: UUID
|
||||||
|
created_at: Union[None, datetime.datetime]
|
||||||
|
state: StateEnum
|
||||||
|
amount: int
|
||||||
|
updated_at: Union[None, datetime.datetime]
|
||||||
|
details: Any
|
||||||
|
role: str
|
||||||
|
payer_id: Union[None, UUID, Unset] = UNSET
|
||||||
|
payer_type: Union[None, PayerTypeEnum, Unset] = UNSET
|
||||||
|
delay: Union[Unset, int] = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
uuid = str(self.uuid)
|
||||||
|
|
||||||
|
payee_id = str(self.payee_id)
|
||||||
|
|
||||||
|
payee_type = str(self.payee_type)
|
||||||
|
|
||||||
|
created_at: Union[None, str]
|
||||||
|
if isinstance(self.created_at, datetime.datetime):
|
||||||
|
created_at = self.created_at.isoformat()
|
||||||
|
else:
|
||||||
|
created_at = self.created_at
|
||||||
|
|
||||||
|
state = self.state.value
|
||||||
|
|
||||||
|
amount = self.amount
|
||||||
|
|
||||||
|
updated_at: Union[None, str]
|
||||||
|
if isinstance(self.updated_at, datetime.datetime):
|
||||||
|
updated_at = self.updated_at.isoformat()
|
||||||
|
else:
|
||||||
|
updated_at = self.updated_at
|
||||||
|
|
||||||
|
details = self.details
|
||||||
|
|
||||||
|
role = self.role
|
||||||
|
|
||||||
|
payer_id: Union[None, Unset, str]
|
||||||
|
if isinstance(self.payer_id, Unset):
|
||||||
|
payer_id = UNSET
|
||||||
|
elif isinstance(self.payer_id, UUID):
|
||||||
|
payer_id = str(self.payer_id)
|
||||||
|
else:
|
||||||
|
payer_id = self.payer_id
|
||||||
|
|
||||||
|
payer_type: Union[None, Unset, int]
|
||||||
|
if isinstance(self.payer_type, Unset):
|
||||||
|
payer_type = UNSET
|
||||||
|
elif isinstance(self.payer_type, PayerTypeEnum):
|
||||||
|
payer_type = self.payer_type.value
|
||||||
|
else:
|
||||||
|
payer_type = self.payer_type
|
||||||
|
|
||||||
|
delay = self.delay
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"uuid": uuid,
|
||||||
|
"payee_id": payee_id,
|
||||||
|
"payee_type": payee_type,
|
||||||
|
"created_at": created_at,
|
||||||
|
"state": state,
|
||||||
|
"amount": amount,
|
||||||
|
"updated_at": updated_at,
|
||||||
|
"details": details,
|
||||||
|
"role": role,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if payer_id is not UNSET:
|
||||||
|
field_dict["payer_id"] = payer_id
|
||||||
|
if payer_type is not UNSET:
|
||||||
|
field_dict["payer_type"] = payer_type
|
||||||
|
if delay is not UNSET:
|
||||||
|
field_dict["delay"] = delay
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
|
payee_id = UUID(d.pop("payee_id"))
|
||||||
|
|
||||||
|
payee_type = UUID(d.pop("payee_type"))
|
||||||
|
|
||||||
|
def _parse_created_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
created_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return created_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
created_at = _parse_created_at(d.pop("created_at"))
|
||||||
|
|
||||||
|
state = StateEnum(d.pop("state"))
|
||||||
|
|
||||||
|
amount = d.pop("amount")
|
||||||
|
|
||||||
|
def _parse_updated_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
updated_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return updated_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
updated_at = _parse_updated_at(d.pop("updated_at"))
|
||||||
|
|
||||||
|
details = d.pop("details")
|
||||||
|
|
||||||
|
role = d.pop("role")
|
||||||
|
|
||||||
|
def _parse_payer_id(data: object) -> Union[None, UUID, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
payer_id_type_0 = UUID(data)
|
||||||
|
|
||||||
|
return payer_id_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, UUID, Unset], data)
|
||||||
|
|
||||||
|
payer_id = _parse_payer_id(d.pop("payer_id", UNSET))
|
||||||
|
|
||||||
|
def _parse_payer_type(data: object) -> Union[None, PayerTypeEnum, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, int):
|
||||||
|
raise TypeError()
|
||||||
|
payer_type_type_0 = PayerTypeEnum(data)
|
||||||
|
|
||||||
|
return payer_type_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, PayerTypeEnum, Unset], data)
|
||||||
|
|
||||||
|
payer_type = _parse_payer_type(d.pop("payer_type", UNSET))
|
||||||
|
|
||||||
|
delay = d.pop("delay", UNSET)
|
||||||
|
|
||||||
|
application_transaction = cls(
|
||||||
|
uuid=uuid,
|
||||||
|
payee_id=payee_id,
|
||||||
|
payee_type=payee_type,
|
||||||
|
created_at=created_at,
|
||||||
|
state=state,
|
||||||
|
amount=amount,
|
||||||
|
updated_at=updated_at,
|
||||||
|
details=details,
|
||||||
|
role=role,
|
||||||
|
payer_id=payer_id,
|
||||||
|
payer_type=payer_type,
|
||||||
|
delay=delay,
|
||||||
|
)
|
||||||
|
|
||||||
|
application_transaction.additional_properties = d
|
||||||
|
return application_transaction
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
239
gooyal_wallet_client/models/application_withdraw.py
Normal file
239
gooyal_wallet_client/models/application_withdraw.py
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
import datetime
|
||||||
|
from typing import Any, TypeVar, Union, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
from dateutil.parser import isoparse
|
||||||
|
|
||||||
|
from ..models.payee_type_enum import PayeeTypeEnum
|
||||||
|
from ..models.payer_type_enum import PayerTypeEnum
|
||||||
|
from ..models.state_enum import StateEnum
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ApplicationWithdraw")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class ApplicationWithdraw:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
uuid (UUID):
|
||||||
|
payee_id (UUID):
|
||||||
|
payee_type (Union[None, PayeeTypeEnum]):
|
||||||
|
created_at (Union[None, datetime.datetime]):
|
||||||
|
state (StateEnum): * `1` - created
|
||||||
|
* `2` - delayed
|
||||||
|
* `3` - pending
|
||||||
|
* `4` - incomplete
|
||||||
|
* `5` - success
|
||||||
|
* `6` - failed
|
||||||
|
* `7` - expected_failure
|
||||||
|
amount (int):
|
||||||
|
updated_at (Union[None, datetime.datetime]):
|
||||||
|
details (Any):
|
||||||
|
payer_id (Union[None, UUID, Unset]):
|
||||||
|
payer_type (Union[None, PayerTypeEnum, Unset]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid: UUID
|
||||||
|
payee_id: UUID
|
||||||
|
payee_type: Union[None, PayeeTypeEnum]
|
||||||
|
created_at: Union[None, datetime.datetime]
|
||||||
|
state: StateEnum
|
||||||
|
amount: int
|
||||||
|
updated_at: Union[None, datetime.datetime]
|
||||||
|
details: Any
|
||||||
|
payer_id: Union[None, UUID, Unset] = UNSET
|
||||||
|
payer_type: Union[None, PayerTypeEnum, Unset] = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
uuid = str(self.uuid)
|
||||||
|
|
||||||
|
payee_id = str(self.payee_id)
|
||||||
|
|
||||||
|
payee_type: Union[None, int]
|
||||||
|
if isinstance(self.payee_type, PayeeTypeEnum):
|
||||||
|
payee_type = self.payee_type.value
|
||||||
|
else:
|
||||||
|
payee_type = self.payee_type
|
||||||
|
|
||||||
|
created_at: Union[None, str]
|
||||||
|
if isinstance(self.created_at, datetime.datetime):
|
||||||
|
created_at = self.created_at.isoformat()
|
||||||
|
else:
|
||||||
|
created_at = self.created_at
|
||||||
|
|
||||||
|
state = self.state.value
|
||||||
|
|
||||||
|
amount = self.amount
|
||||||
|
|
||||||
|
updated_at: Union[None, str]
|
||||||
|
if isinstance(self.updated_at, datetime.datetime):
|
||||||
|
updated_at = self.updated_at.isoformat()
|
||||||
|
else:
|
||||||
|
updated_at = self.updated_at
|
||||||
|
|
||||||
|
details = self.details
|
||||||
|
|
||||||
|
payer_id: Union[None, Unset, str]
|
||||||
|
if isinstance(self.payer_id, Unset):
|
||||||
|
payer_id = UNSET
|
||||||
|
elif isinstance(self.payer_id, UUID):
|
||||||
|
payer_id = str(self.payer_id)
|
||||||
|
else:
|
||||||
|
payer_id = self.payer_id
|
||||||
|
|
||||||
|
payer_type: Union[None, Unset, int]
|
||||||
|
if isinstance(self.payer_type, Unset):
|
||||||
|
payer_type = UNSET
|
||||||
|
elif isinstance(self.payer_type, PayerTypeEnum):
|
||||||
|
payer_type = self.payer_type.value
|
||||||
|
else:
|
||||||
|
payer_type = self.payer_type
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"uuid": uuid,
|
||||||
|
"payee_id": payee_id,
|
||||||
|
"payee_type": payee_type,
|
||||||
|
"created_at": created_at,
|
||||||
|
"state": state,
|
||||||
|
"amount": amount,
|
||||||
|
"updated_at": updated_at,
|
||||||
|
"details": details,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if payer_id is not UNSET:
|
||||||
|
field_dict["payer_id"] = payer_id
|
||||||
|
if payer_type is not UNSET:
|
||||||
|
field_dict["payer_type"] = payer_type
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
|
payee_id = UUID(d.pop("payee_id"))
|
||||||
|
|
||||||
|
def _parse_payee_type(data: object) -> Union[None, PayeeTypeEnum]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, int):
|
||||||
|
raise TypeError()
|
||||||
|
payee_type_type_0 = PayeeTypeEnum(data)
|
||||||
|
|
||||||
|
return payee_type_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, PayeeTypeEnum], data)
|
||||||
|
|
||||||
|
payee_type = _parse_payee_type(d.pop("payee_type"))
|
||||||
|
|
||||||
|
def _parse_created_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
created_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return created_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
created_at = _parse_created_at(d.pop("created_at"))
|
||||||
|
|
||||||
|
state = StateEnum(d.pop("state"))
|
||||||
|
|
||||||
|
amount = d.pop("amount")
|
||||||
|
|
||||||
|
def _parse_updated_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
updated_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return updated_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
updated_at = _parse_updated_at(d.pop("updated_at"))
|
||||||
|
|
||||||
|
details = d.pop("details")
|
||||||
|
|
||||||
|
def _parse_payer_id(data: object) -> Union[None, UUID, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
payer_id_type_0 = UUID(data)
|
||||||
|
|
||||||
|
return payer_id_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, UUID, Unset], data)
|
||||||
|
|
||||||
|
payer_id = _parse_payer_id(d.pop("payer_id", UNSET))
|
||||||
|
|
||||||
|
def _parse_payer_type(data: object) -> Union[None, PayerTypeEnum, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, int):
|
||||||
|
raise TypeError()
|
||||||
|
payer_type_type_0 = PayerTypeEnum(data)
|
||||||
|
|
||||||
|
return payer_type_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, PayerTypeEnum, Unset], data)
|
||||||
|
|
||||||
|
payer_type = _parse_payer_type(d.pop("payer_type", UNSET))
|
||||||
|
|
||||||
|
application_withdraw = cls(
|
||||||
|
uuid=uuid,
|
||||||
|
payee_id=payee_id,
|
||||||
|
payee_type=payee_type,
|
||||||
|
created_at=created_at,
|
||||||
|
state=state,
|
||||||
|
amount=amount,
|
||||||
|
updated_at=updated_at,
|
||||||
|
details=details,
|
||||||
|
payer_id=payer_id,
|
||||||
|
payer_type=payer_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
application_withdraw.additional_properties = d
|
||||||
|
return application_withdraw
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
58
gooyal_wallet_client/models/my_wallet.py
Normal file
58
gooyal_wallet_client/models/my_wallet.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
from typing import Any, TypeVar, Union
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="MyWallet")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class MyWallet:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
balance (Union[Unset, int]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
balance: Union[Unset, int] = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
balance = self.balance
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update({})
|
||||||
|
if balance is not UNSET:
|
||||||
|
field_dict["balance"] = balance
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
balance = d.pop("balance", UNSET)
|
||||||
|
|
||||||
|
my_wallet = cls(
|
||||||
|
balance=balance,
|
||||||
|
)
|
||||||
|
|
||||||
|
my_wallet.additional_properties = d
|
||||||
|
return my_wallet
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -7,24 +6,24 @@ from attrs import field as _attrs_field
|
||||||
from ..types import UNSET, Unset
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..models.public_user import PublicUser
|
from ..models.application_transaction import ApplicationTransaction
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="PaginatedPublicUserList")
|
T = TypeVar("T", bound="PaginatedApplicationTransactionList")
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
@_attrs_define
|
||||||
class PaginatedPublicUserList:
|
class PaginatedApplicationTransactionList:
|
||||||
"""
|
"""
|
||||||
Attributes:
|
Attributes:
|
||||||
count (int): Example: 123.
|
count (int): Example: 123.
|
||||||
results (list['PublicUser']):
|
results (list['ApplicationTransaction']):
|
||||||
next_ (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=4.
|
next_ (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=4.
|
||||||
previous (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=2.
|
previous (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
count: int
|
count: int
|
||||||
results: list["PublicUser"]
|
results: list["ApplicationTransaction"]
|
||||||
next_: Union[None, Unset, str] = UNSET
|
next_: Union[None, Unset, str] = UNSET
|
||||||
previous: Union[None, Unset, str] = UNSET
|
previous: Union[None, Unset, str] = UNSET
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
@ -65,16 +64,16 @@ class PaginatedPublicUserList:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
from ..models.public_user import PublicUser
|
from ..models.application_transaction import ApplicationTransaction
|
||||||
|
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
count = d.pop("count")
|
count = d.pop("count")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
_results = d.pop("results")
|
_results = d.pop("results")
|
||||||
for results_item_data in _results:
|
for results_item_data in _results:
|
||||||
results_item = PublicUser.from_dict(results_item_data)
|
results_item = ApplicationTransaction.from_dict(results_item_data)
|
||||||
|
|
||||||
results.append(results_item)
|
results.append(results_item)
|
||||||
|
|
||||||
|
|
@ -96,15 +95,15 @@ class PaginatedPublicUserList:
|
||||||
|
|
||||||
previous = _parse_previous(d.pop("previous", UNSET))
|
previous = _parse_previous(d.pop("previous", UNSET))
|
||||||
|
|
||||||
paginated_public_user_list = cls(
|
paginated_application_transaction_list = cls(
|
||||||
count=count,
|
count=count,
|
||||||
results=results,
|
results=results,
|
||||||
next_=next_,
|
next_=next_,
|
||||||
previous=previous,
|
previous=previous,
|
||||||
)
|
)
|
||||||
|
|
||||||
paginated_public_user_list.additional_properties = d
|
paginated_application_transaction_list.additional_properties = d
|
||||||
return paginated_public_user_list
|
return paginated_application_transaction_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_keys(self) -> list[str]:
|
def additional_keys(self) -> list[str]:
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
from typing import TYPE_CHECKING, Any, TypeVar, Union, cast
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
from attrs import define as _attrs_define
|
||||||
|
|
@ -7,24 +6,24 @@ from attrs import field as _attrs_field
|
||||||
from ..types import UNSET, Unset
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..models.application import Application
|
from ..models.user_transaction import UserTransaction
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="PaginatedApplicationList")
|
T = TypeVar("T", bound="PaginatedUserTransactionList")
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
@_attrs_define
|
||||||
class PaginatedApplicationList:
|
class PaginatedUserTransactionList:
|
||||||
"""
|
"""
|
||||||
Attributes:
|
Attributes:
|
||||||
count (int): Example: 123.
|
count (int): Example: 123.
|
||||||
results (list['Application']):
|
results (list['UserTransaction']):
|
||||||
next_ (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=4.
|
next_ (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=4.
|
||||||
previous (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=2.
|
previous (Union[None, Unset, str]): Example: http://api.example.org/accounts/?page=2.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
count: int
|
count: int
|
||||||
results: list["Application"]
|
results: list["UserTransaction"]
|
||||||
next_: Union[None, Unset, str] = UNSET
|
next_: Union[None, Unset, str] = UNSET
|
||||||
previous: Union[None, Unset, str] = UNSET
|
previous: Union[None, Unset, str] = UNSET
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
@ -65,16 +64,16 @@ class PaginatedApplicationList:
|
||||||
return field_dict
|
return field_dict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
from ..models.application import Application
|
from ..models.user_transaction import UserTransaction
|
||||||
|
|
||||||
d = dict(src_dict)
|
d = src_dict.copy()
|
||||||
count = d.pop("count")
|
count = d.pop("count")
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
_results = d.pop("results")
|
_results = d.pop("results")
|
||||||
for results_item_data in _results:
|
for results_item_data in _results:
|
||||||
results_item = Application.from_dict(results_item_data)
|
results_item = UserTransaction.from_dict(results_item_data)
|
||||||
|
|
||||||
results.append(results_item)
|
results.append(results_item)
|
||||||
|
|
||||||
|
|
@ -96,15 +95,15 @@ class PaginatedApplicationList:
|
||||||
|
|
||||||
previous = _parse_previous(d.pop("previous", UNSET))
|
previous = _parse_previous(d.pop("previous", UNSET))
|
||||||
|
|
||||||
paginated_application_list = cls(
|
paginated_user_transaction_list = cls(
|
||||||
count=count,
|
count=count,
|
||||||
results=results,
|
results=results,
|
||||||
next_=next_,
|
next_=next_,
|
||||||
previous=previous,
|
previous=previous,
|
||||||
)
|
)
|
||||||
|
|
||||||
paginated_application_list.additional_properties = d
|
paginated_user_transaction_list.additional_properties = d
|
||||||
return paginated_application_list
|
return paginated_user_transaction_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_keys(self) -> list[str]:
|
def additional_keys(self) -> list[str]:
|
||||||
9
gooyal_wallet_client/models/payee_type_enum.py
Normal file
9
gooyal_wallet_client/models/payee_type_enum.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class PayeeTypeEnum(IntEnum):
|
||||||
|
VALUE_1 = 1
|
||||||
|
VALUE_2 = 2
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
9
gooyal_wallet_client/models/payer_type_enum.py
Normal file
9
gooyal_wallet_client/models/payer_type_enum.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class PayerTypeEnum(IntEnum):
|
||||||
|
VALUE_1 = 1
|
||||||
|
VALUE_2 = 2
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
238
gooyal_wallet_client/models/user_transaction.py
Normal file
238
gooyal_wallet_client/models/user_transaction.py
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
import datetime
|
||||||
|
from typing import Any, TypeVar, Union, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
from dateutil.parser import isoparse
|
||||||
|
|
||||||
|
from ..models.payer_type_enum import PayerTypeEnum
|
||||||
|
from ..models.state_enum import StateEnum
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="UserTransaction")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class UserTransaction:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
uuid (UUID):
|
||||||
|
payee_id (UUID):
|
||||||
|
payee_type (UUID):
|
||||||
|
created_at (Union[None, datetime.datetime]):
|
||||||
|
state (StateEnum): * `1` - created
|
||||||
|
* `2` - delayed
|
||||||
|
* `3` - pending
|
||||||
|
* `4` - incomplete
|
||||||
|
* `5` - success
|
||||||
|
* `6` - failed
|
||||||
|
* `7` - expected_failure
|
||||||
|
amount (int):
|
||||||
|
updated_at (Union[None, datetime.datetime]):
|
||||||
|
details (Any):
|
||||||
|
role (int):
|
||||||
|
payer_id (Union[None, UUID, Unset]):
|
||||||
|
payer_type (Union[None, PayerTypeEnum, Unset]):
|
||||||
|
delay (Union[Unset, int]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid: UUID
|
||||||
|
payee_id: UUID
|
||||||
|
payee_type: UUID
|
||||||
|
created_at: Union[None, datetime.datetime]
|
||||||
|
state: StateEnum
|
||||||
|
amount: int
|
||||||
|
updated_at: Union[None, datetime.datetime]
|
||||||
|
details: Any
|
||||||
|
role: int
|
||||||
|
payer_id: Union[None, UUID, Unset] = UNSET
|
||||||
|
payer_type: Union[None, PayerTypeEnum, Unset] = UNSET
|
||||||
|
delay: Union[Unset, int] = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
uuid = str(self.uuid)
|
||||||
|
|
||||||
|
payee_id = str(self.payee_id)
|
||||||
|
|
||||||
|
payee_type = str(self.payee_type)
|
||||||
|
|
||||||
|
created_at: Union[None, str]
|
||||||
|
if isinstance(self.created_at, datetime.datetime):
|
||||||
|
created_at = self.created_at.isoformat()
|
||||||
|
else:
|
||||||
|
created_at = self.created_at
|
||||||
|
|
||||||
|
state = self.state.value
|
||||||
|
|
||||||
|
amount = self.amount
|
||||||
|
|
||||||
|
updated_at: Union[None, str]
|
||||||
|
if isinstance(self.updated_at, datetime.datetime):
|
||||||
|
updated_at = self.updated_at.isoformat()
|
||||||
|
else:
|
||||||
|
updated_at = self.updated_at
|
||||||
|
|
||||||
|
details = self.details
|
||||||
|
|
||||||
|
role = self.role
|
||||||
|
|
||||||
|
payer_id: Union[None, Unset, str]
|
||||||
|
if isinstance(self.payer_id, Unset):
|
||||||
|
payer_id = UNSET
|
||||||
|
elif isinstance(self.payer_id, UUID):
|
||||||
|
payer_id = str(self.payer_id)
|
||||||
|
else:
|
||||||
|
payer_id = self.payer_id
|
||||||
|
|
||||||
|
payer_type: Union[None, Unset, int]
|
||||||
|
if isinstance(self.payer_type, Unset):
|
||||||
|
payer_type = UNSET
|
||||||
|
elif isinstance(self.payer_type, PayerTypeEnum):
|
||||||
|
payer_type = self.payer_type.value
|
||||||
|
else:
|
||||||
|
payer_type = self.payer_type
|
||||||
|
|
||||||
|
delay = self.delay
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"uuid": uuid,
|
||||||
|
"payee_id": payee_id,
|
||||||
|
"payee_type": payee_type,
|
||||||
|
"created_at": created_at,
|
||||||
|
"state": state,
|
||||||
|
"amount": amount,
|
||||||
|
"updated_at": updated_at,
|
||||||
|
"details": details,
|
||||||
|
"role": role,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if payer_id is not UNSET:
|
||||||
|
field_dict["payer_id"] = payer_id
|
||||||
|
if payer_type is not UNSET:
|
||||||
|
field_dict["payer_type"] = payer_type
|
||||||
|
if delay is not UNSET:
|
||||||
|
field_dict["delay"] = delay
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
|
payee_id = UUID(d.pop("payee_id"))
|
||||||
|
|
||||||
|
payee_type = UUID(d.pop("payee_type"))
|
||||||
|
|
||||||
|
def _parse_created_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
created_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return created_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
created_at = _parse_created_at(d.pop("created_at"))
|
||||||
|
|
||||||
|
state = StateEnum(d.pop("state"))
|
||||||
|
|
||||||
|
amount = d.pop("amount")
|
||||||
|
|
||||||
|
def _parse_updated_at(data: object) -> Union[None, datetime.datetime]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
updated_at_type_0 = isoparse(data)
|
||||||
|
|
||||||
|
return updated_at_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, datetime.datetime], data)
|
||||||
|
|
||||||
|
updated_at = _parse_updated_at(d.pop("updated_at"))
|
||||||
|
|
||||||
|
details = d.pop("details")
|
||||||
|
|
||||||
|
role = d.pop("role")
|
||||||
|
|
||||||
|
def _parse_payer_id(data: object) -> Union[None, UUID, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
payer_id_type_0 = UUID(data)
|
||||||
|
|
||||||
|
return payer_id_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, UUID, Unset], data)
|
||||||
|
|
||||||
|
payer_id = _parse_payer_id(d.pop("payer_id", UNSET))
|
||||||
|
|
||||||
|
def _parse_payer_type(data: object) -> Union[None, PayerTypeEnum, Unset]:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, int):
|
||||||
|
raise TypeError()
|
||||||
|
payer_type_type_0 = PayerTypeEnum(data)
|
||||||
|
|
||||||
|
return payer_type_type_0
|
||||||
|
except: # noqa: E722
|
||||||
|
pass
|
||||||
|
return cast(Union[None, PayerTypeEnum, Unset], data)
|
||||||
|
|
||||||
|
payer_type = _parse_payer_type(d.pop("payer_type", UNSET))
|
||||||
|
|
||||||
|
delay = d.pop("delay", UNSET)
|
||||||
|
|
||||||
|
user_transaction = cls(
|
||||||
|
uuid=uuid,
|
||||||
|
payee_id=payee_id,
|
||||||
|
payee_type=payee_type,
|
||||||
|
created_at=created_at,
|
||||||
|
state=state,
|
||||||
|
amount=amount,
|
||||||
|
updated_at=updated_at,
|
||||||
|
details=details,
|
||||||
|
role=role,
|
||||||
|
payer_id=payer_id,
|
||||||
|
payer_type=payer_type,
|
||||||
|
delay=delay,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_transaction.additional_properties = d
|
||||||
|
return user_transaction
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
|
|
@ -36,7 +36,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.postgres',
|
|
||||||
|
|
||||||
# pip installed apps
|
# pip installed apps
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
|
|
@ -189,7 +188,7 @@ REST_FRAMEWORK = {
|
||||||
# 'otp_min': '1/min',
|
# 'otp_min': '1/min',
|
||||||
# 'otp_day': '20/day',
|
# 'otp_day': '20/day',
|
||||||
},
|
},
|
||||||
'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
|
'EXCEPTION_HANDLER': 'utils.exceptions.ExceptionHandler',
|
||||||
}
|
}
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
|
@ -232,7 +231,7 @@ SPECTACULAR_SETTINGS = {
|
||||||
AUTH_USER_MODEL = 'users.User'
|
AUTH_USER_MODEL = 'users.User'
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
CORS_ALLOW_ALL_ORIGINS = True
|
CORS_ALLOW_ALL_ORIGINS = True
|
||||||
CSRF_TRUSTED_ORIGINS = ['http://*', 'https://*', 'http://*.gooyal.com', 'https://*.gooyal.com', 'http://*.gooyal.ir', 'https://*.gooyal.ir', ]
|
CSRF_TRUSTED_ORIGINS = ['http://*', 'https://*', 'http://*.gooyal.com', 'https://*.gooyal.com']
|
||||||
|
|
||||||
JALALI_DATE_DEFAULTS = {
|
JALALI_DATE_DEFAULTS = {
|
||||||
'Strftime': {
|
'Strftime': {
|
||||||
|
|
@ -251,14 +250,20 @@ CACHES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
'formatters': {
|
'formatters': {
|
||||||
|
# 'verbose': {
|
||||||
|
# 'format': '{levelname} AT: {asctime} LINE: {lineno} LOGGER: {name} FILE: {pathname} MESSAGE: {message}',
|
||||||
|
# 'style': '{',
|
||||||
|
# },
|
||||||
|
# 'simple': {
|
||||||
|
# 'format': '{levelname} {message}',
|
||||||
|
# 'style': '{',
|
||||||
|
# },
|
||||||
'loki': {
|
'loki': {
|
||||||
'class': 'utils.logs.LokiFormatter', # required
|
'class': "utils.logs.LokiFormatter", # required
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -268,23 +273,32 @@ LOGGING = {
|
||||||
'class': 'utils.logs.LokiHandler', # Required
|
'class': 'utils.logs.LokiHandler', # Required
|
||||||
'formatter': 'loki', # Loki formatter. Required
|
'formatter': 'loki', # Loki formatter. Required
|
||||||
'timeout': 1, # Post request timeout, default is 0.5. Optional
|
'timeout': 1, # Post request timeout, default is 0.5. Optional
|
||||||
'url': f'{LOKI_BASE_PUBLIC_URL}/loki/api/v1/push', # Loki url. Defaults to localhost. Optional.
|
'url': 'https://loki.winsoo.ir/loki/api/v1/push', # Loki url. Defaults to localhost. Optional.
|
||||||
# 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional
|
# 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional
|
||||||
'tags': {"app": "promotions", "env": "production"}, # Tags / Labels to attach to the log. Optional, but strongly encoraged to use.
|
'tags': {"app": "ads", "env": "production"},
|
||||||
'mode': 'thread', # Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional.
|
# Tags / Labels to attach to the log. Optional, but strongly encoraged to use.
|
||||||
},
|
'mode': 'thread',
|
||||||
'console': {
|
# Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional.
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
# 'formatter': 'verbose',
|
|
||||||
},
|
},
|
||||||
|
# 'file': {
|
||||||
|
# 'level': 'DEBUG',
|
||||||
|
# 'class': 'logging.handlers.TimedRotatingFileHandler',
|
||||||
|
# 'filename': 'logs/app.log',
|
||||||
|
# 'formatter': 'verbose',
|
||||||
|
# "when": 'D',
|
||||||
|
# "backupCount": 5,
|
||||||
|
# },
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'': {
|
'django': {
|
||||||
'handlers': ['console', 'loki'],
|
'handlers': ['loki'],
|
||||||
'level': 'INFO',
|
'level': 'INFO',
|
||||||
'propagate': True,
|
|
||||||
},
|
},
|
||||||
|
# 'root': {
|
||||||
|
# 'handlers': ['file'],
|
||||||
|
# 'level': 'DEBUG',
|
||||||
|
# 'propagate': True,
|
||||||
|
# },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,30 +308,6 @@ from typing import List, Tuple
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django_minio_backend.models.MinioBackend",
|
"BACKEND": "django_minio_backend.models.MinioBackend",
|
||||||
"OPTIONS": {
|
|
||||||
"MINIO_ENDPOINT": config('MINIO_ENDPOINT', default='drive.gooyal.com'),
|
|
||||||
"MINIO_USE_HTTPS": config('MINIO_USE_HTTPS', default=True, cast=bool),
|
|
||||||
|
|
||||||
"MINIO_EXTERNAL_ENDPOINT": config('MINIO_EXTERNAL_ENDPOINT', default='drive.gooyal.com'), # Default is same as MINIO_ENDPOINT
|
|
||||||
"MINIO_EXTERNAL_ENDPOINT_USE_HTTPS": config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True, cast=bool), # Default is same as MINIO_USE_HTTPS
|
|
||||||
"MINIO_REGION": None, # Default is set to None
|
|
||||||
"MINIO_ACCESS_KEY": config('MINIO_ACCESS_KEY'),
|
|
||||||
"MINIO_SECRET_KEY": config('MINIO_SECRET_KEY'),
|
|
||||||
"MINIO_URL_EXPIRY_HOURS": timedelta(days=1), # Default is 7 days (longest) if not defined
|
|
||||||
"MINIO_CONSISTENCY_CHECK_ON_START": True,
|
|
||||||
"MINIO_DEFAULT_BUCKET": config('MINIO_MEDIA_FILES_BUCKET'), # replacement for MEDIA_ROOT
|
|
||||||
# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT
|
|
||||||
|
|
||||||
"MINIO_PRIVATE_BUCKETS": [
|
|
||||||
'default',
|
|
||||||
],
|
|
||||||
"MINIO_PUBLIC_BUCKETS": [
|
|
||||||
# 'default-public',
|
|
||||||
config('MINIO_MEDIA_FILES_BUCKET')
|
|
||||||
],
|
|
||||||
"MINIO_POLICY_HOOKS": [],
|
|
||||||
"MINIO_BUCKET_CHECK_ON_SAVE": True # Default: True // Creates bucket if missing, then save
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"staticfiles": {
|
"staticfiles": {
|
||||||
# "BACKEND": "django_minio_backend.models.MinioBackendStatic",
|
# "BACKEND": "django_minio_backend.models.MinioBackendStatic",
|
||||||
|
|
@ -337,7 +327,7 @@ MINIO_REGION = None # Default is set to None
|
||||||
MINIO_ACCESS_KEY = config('MINIO_ACCESS_KEY')
|
MINIO_ACCESS_KEY = config('MINIO_ACCESS_KEY')
|
||||||
MINIO_SECRET_KEY = config('MINIO_SECRET_KEY')
|
MINIO_SECRET_KEY = config('MINIO_SECRET_KEY')
|
||||||
MINIO_URL_EXPIRY_HOURS = timedelta(days=1) # Default is 7 days (longest) if not defined
|
MINIO_URL_EXPIRY_HOURS = timedelta(days=1) # Default is 7 days (longest) if not defined
|
||||||
MINIO_CONSISTENCY_CHECK_ON_START = True
|
MINIO_CONSISTENCY_CHECK_ON_START = False
|
||||||
MINIO_MEDIA_FILES_BUCKET = config('MINIO_MEDIA_FILES_BUCKET') # replacement for MEDIA_ROOT
|
MINIO_MEDIA_FILES_BUCKET = config('MINIO_MEDIA_FILES_BUCKET') # replacement for MEDIA_ROOT
|
||||||
# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT
|
# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT
|
||||||
|
|
||||||
|
|
@ -381,18 +371,3 @@ CELERY_TASK_TRACK_STARTED = True
|
||||||
CELERY_TASK_TIME_LIMIT = 30 * 60
|
CELERY_TASK_TIME_LIMIT = 30 * 60
|
||||||
CELERY_BROKER_URL = REDIS_BASE_URL
|
CELERY_BROKER_URL = REDIS_BASE_URL
|
||||||
CELERY_RESULT_BACKEND = REDIS_BASE_URL
|
CELERY_RESULT_BACKEND = REDIS_BASE_URL
|
||||||
|
|
||||||
ACCOUNTS_BASE_PUBLIC_URL = config('ACCOUNTS_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
|
|
||||||
WALLET_BASE_PUBLIC_URL = config('WALLET_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
WALLET_RIAL_DEPOSIT = config('WALLET_RIAL_DEPOSIT', cast=str)
|
|
||||||
WALLET_USER_BILLBOARD_VISIT_INCOME = config('WALLET_USER_BILLBOARD_VISIT_INCOME', cast=str)
|
|
||||||
# Company-side pool promotion payouts are drawn from; must be distinct from the user-side
|
|
||||||
# wallet above. TODO(you): replace with the real wallet-type UUID from the wallet service.
|
|
||||||
WALLET_PROMOTIONS_CREDIT = config('WALLET_PROMOTIONS_CREDIT', cast=str)
|
|
||||||
# Same wallet-service UUID as the advertising repo's own WALLET_ADVERTISING_TRANSIT setting.
|
|
||||||
# Destination for a Recipient whose wallet_destination is advertising_transit — billboard/ad
|
|
||||||
# credit rather than a cash-like user reward — see Recipient.get_wallet_category_uuid().
|
|
||||||
WALLET_ADVERTISING_TRANSIT = config('WALLET_ADVERTISING_TRANSIT', cast=str)
|
|
||||||
|
|
||||||
NOTIFICATIONS_BASE_PUBLIC_URL = config('NOTIFICATIONS_BASE_PUBLIC_URL', default=None, cast=str)
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from django.urls import path, include
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('swagger/', SpectacularAPIView.as_view(), name='schema'),
|
path('swagger/', SpectacularAPIView.as_view(), name='schema'),
|
||||||
path('swagger/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
path('swagger/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||||
|
|
@ -28,8 +29,7 @@ urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('', include('utils.urls')),
|
path('', include('utils.urls')),
|
||||||
path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')),
|
path('oauth2/', include('oauth2_provider.urls', namespace='oauth2_provider')),
|
||||||
path('promotions/', include('apps.promotions.urls_user', namespace='promotions')),
|
path('promotions/', include('apps.promotions.urls', namespace='promotions')),
|
||||||
path('api/v2/promotions/application/<user_uuid>/', include('apps.promotions.urls_application', namespace='promotions-application')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
|
||||||
112
requirements.txt
112
requirements.txt
|
|
@ -1,42 +1,44 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.13
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile
|
# pip-compile
|
||||||
#
|
#
|
||||||
amqp==5.3.1
|
amqp==5.3.1
|
||||||
# via kombu
|
# via kombu
|
||||||
anyio==4.12.1
|
anyio==4.9.0
|
||||||
# via httpx
|
# via httpx
|
||||||
argon2-cffi==25.1.0
|
argon2-cffi==25.1.0
|
||||||
# via minio
|
# via minio
|
||||||
argon2-cffi-bindings==25.1.0
|
argon2-cffi-bindings==21.2.0
|
||||||
# via argon2-cffi
|
# via argon2-cffi
|
||||||
asgiref==3.11.1
|
asgiref==3.9.1
|
||||||
# via
|
# via
|
||||||
# django
|
# django
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
attrs==25.4.0
|
async-timeout==5.0.1
|
||||||
|
# via redis
|
||||||
|
attrs==25.3.0
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
billiard==4.2.4
|
billiard==4.2.1
|
||||||
# via celery
|
# via celery
|
||||||
celery==5.6.2
|
celery==5.5.3
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
certifi==2026.1.4
|
certifi==2025.7.14
|
||||||
# via
|
# via
|
||||||
# httpcore
|
# httpcore
|
||||||
# httpx
|
# httpx
|
||||||
# minio
|
# minio
|
||||||
# requests
|
# requests
|
||||||
cffi==2.0.0
|
cffi==1.17.1
|
||||||
# via
|
# via
|
||||||
# argon2-cffi-bindings
|
# argon2-cffi-bindings
|
||||||
# cryptography
|
# cryptography
|
||||||
charset-normalizer==3.4.4
|
charset-normalizer==3.4.2
|
||||||
# via requests
|
# via requests
|
||||||
click==8.3.1
|
click==8.2.1
|
||||||
# via
|
# via
|
||||||
# celery
|
# celery
|
||||||
# click-didyoumean
|
# click-didyoumean
|
||||||
|
|
@ -50,9 +52,9 @@ click-repl==0.3.0
|
||||||
# via celery
|
# via celery
|
||||||
crispy-bootstrap5==2025.6
|
crispy-bootstrap5==2025.6
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
cryptography==46.0.5
|
cryptography==45.0.5
|
||||||
# via jwcrypto
|
# via jwcrypto
|
||||||
django==6.0.2
|
django==5.2.4
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
|
|
@ -65,33 +67,33 @@ django==6.0.2
|
||||||
# django-redis
|
# django-redis
|
||||||
# djangorestframework
|
# djangorestframework
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
django-cors-headers==4.9.0
|
django-cors-headers==4.7.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-crispy-forms==2.5
|
django-crispy-forms==2.4
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
django-filter==25.2
|
django-filter==25.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-jalali-date==2.0.0
|
django-jalali-date==2.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-minio-backend==4.5.0
|
django-minio-backend==3.8.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-oauth-toolkit==3.2.0
|
django-oauth-toolkit==3.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-redis==6.0.0
|
django-redis==6.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
djangorestframework==3.16.1
|
djangorestframework==3.16.0
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
drf-spectacular==0.29.0
|
drf-spectacular==0.28.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
gevent==25.9.1
|
gevent==25.5.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
greenlet==3.3.2
|
greenlet==3.2.3
|
||||||
# via gevent
|
# via gevent
|
||||||
gunicorn==25.1.0
|
gunicorn==23.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
h11==0.16.0
|
h11==0.16.0
|
||||||
# via httpcore
|
# via httpcore
|
||||||
|
|
@ -99,7 +101,7 @@ httpcore==1.0.9
|
||||||
# via httpx
|
# via httpx
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.11
|
idna==3.10
|
||||||
# via
|
# via
|
||||||
# anyio
|
# anyio
|
||||||
# httpx
|
# httpx
|
||||||
|
|
@ -110,35 +112,33 @@ jalali-core==1.0.0
|
||||||
# via jdatetime
|
# via jdatetime
|
||||||
jdatetime==5.2.0
|
jdatetime==5.2.0
|
||||||
# via django-jalali-date
|
# via django-jalali-date
|
||||||
jsonschema==4.26.0
|
jsonschema==4.25.0
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
jsonschema-specifications==2025.9.1
|
jsonschema-specifications==2025.4.1
|
||||||
# via jsonschema
|
# via jsonschema
|
||||||
jwcrypto==1.5.6
|
jwcrypto==1.5.6
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
kombu==5.6.2
|
kombu==5.5.4
|
||||||
# via celery
|
# via celery
|
||||||
minio==7.2.20
|
minio==7.2.16
|
||||||
# via django-minio-backend
|
# via django-minio-backend
|
||||||
oauthlib==3.3.1
|
oauthlib==3.3.1
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
packaging==26.0
|
packaging==25.0
|
||||||
# via
|
# via
|
||||||
# gunicorn
|
# gunicorn
|
||||||
# kombu
|
# kombu
|
||||||
pillow==12.1.1
|
pillow==11.3.0
|
||||||
# via
|
|
||||||
# -r requirements.in
|
|
||||||
# django-minio-backend
|
|
||||||
prompt-toolkit==3.0.52
|
|
||||||
# via click-repl
|
|
||||||
psycopg[binary,pool]==3.3.3
|
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
psycopg-binary==3.3.3
|
prompt-toolkit==3.0.51
|
||||||
|
# via click-repl
|
||||||
|
psycopg[binary,pool]==3.2.9
|
||||||
|
# via -r requirements.in
|
||||||
|
psycopg-binary==3.2.9
|
||||||
# via psycopg
|
# via psycopg
|
||||||
psycopg-pool==3.3.0
|
psycopg-pool==3.2.6
|
||||||
# via psycopg
|
# via psycopg
|
||||||
pycparser==3.0
|
pycparser==2.22
|
||||||
# via cffi
|
# via cffi
|
||||||
pycryptodome==3.23.0
|
pycryptodome==3.23.0
|
||||||
# via minio
|
# via minio
|
||||||
|
|
@ -146,40 +146,43 @@ python-dateutil==2.9.0.post0
|
||||||
# via celery
|
# via celery
|
||||||
python-decouple==3.8
|
python-decouple==3.8
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pyyaml==6.0.3
|
pyyaml==6.0.2
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
redis==7.2.0
|
redis==6.2.0
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-redis
|
# django-redis
|
||||||
referencing==0.37.0
|
referencing==0.36.2
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# jsonschema-specifications
|
# jsonschema-specifications
|
||||||
requests==2.32.5
|
requests==2.32.4
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-oauth-toolkit
|
# django-oauth-toolkit
|
||||||
rpds-py==0.30.0
|
rpds-py==0.26.0
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
# via python-dateutil
|
# via python-dateutil
|
||||||
sqlparse==0.5.5
|
sniffio==1.3.1
|
||||||
|
# via anyio
|
||||||
|
sqlparse==0.5.3
|
||||||
# via django
|
# via django
|
||||||
typing-extensions==4.15.0
|
typing-extensions==4.14.1
|
||||||
# via
|
# via
|
||||||
|
# anyio
|
||||||
# jwcrypto
|
# jwcrypto
|
||||||
# minio
|
# minio
|
||||||
|
# psycopg
|
||||||
# psycopg-pool
|
# psycopg-pool
|
||||||
tzdata==2025.3
|
# referencing
|
||||||
|
tzdata==2025.2
|
||||||
# via kombu
|
# via kombu
|
||||||
tzlocal==5.3.1
|
|
||||||
# via celery
|
|
||||||
uritemplate==4.2.0
|
uritemplate==4.2.0
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
urllib3==2.6.3
|
urllib3==2.5.0
|
||||||
# via
|
# via
|
||||||
# minio
|
# minio
|
||||||
# requests
|
# requests
|
||||||
|
|
@ -188,9 +191,12 @@ vine==5.1.0
|
||||||
# amqp
|
# amqp
|
||||||
# celery
|
# celery
|
||||||
# kombu
|
# kombu
|
||||||
wcwidth==0.6.0
|
wcwidth==0.2.13
|
||||||
# via prompt-toolkit
|
# via prompt-toolkit
|
||||||
zope-event==6.1
|
zope-event==5.1.1
|
||||||
# via gevent
|
# via gevent
|
||||||
zope-interface==8.2
|
zope-interface==7.2
|
||||||
# via gevent
|
# via gevent
|
||||||
|
|
||||||
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
# setuptools
|
||||||
|
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import time
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.cache import cache
|
|
||||||
from httpx import Request
|
|
||||||
|
|
||||||
from utils.clients.gooyal_accounts_client import AuthenticatedClient
|
|
||||||
from utils.clients.gooyal_accounts_client.models import PublicUser, Application
|
|
||||||
from utils.clients.gooyal_accounts_client.api.users import users_api_users_retrieve
|
|
||||||
from utils.clients.gooyal_accounts_client.api.oauth2 import oauth2_apis_applications_retrieve
|
|
||||||
from utils.clients.gooyal_accounts_client.models import Account
|
|
||||||
from utils.clients.gooyal_accounts_client.api.users import (users_api_users_details_retrieve)
|
|
||||||
|
|
||||||
|
|
||||||
def login_as_client_credentials():
|
|
||||||
access_token = cache.get('promotions_access_token')
|
|
||||||
# expires_in = self.auth_data.get('expires_in')
|
|
||||||
# created_at = self.auth_data.get('created_at')
|
|
||||||
#
|
|
||||||
# if access_token and expires_in > time.time() - created_at:
|
|
||||||
# return self.auth_data
|
|
||||||
if access_token:
|
|
||||||
print('already in cache')
|
|
||||||
return access_token
|
|
||||||
|
|
||||||
print('not in cache')
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"grant_type": 'client_credentials',
|
|
||||||
"scope": settings.OAUTH2_SCOPES,
|
|
||||||
}
|
|
||||||
auth = (settings.OAUTH2_CLIENT_ID, settings.OAUTH2_CLIENT_SECRET)
|
|
||||||
|
|
||||||
response = requests.post(f'{settings.OAUTH2_PROVIDER_PUBLIC_URL}/token/',
|
|
||||||
data=data,
|
|
||||||
auth=auth)
|
|
||||||
|
|
||||||
auth_data = response.json()
|
|
||||||
|
|
||||||
print(auth_data)
|
|
||||||
if 'access_token' in auth_data:
|
|
||||||
auth_data['created_at'] = time.time()
|
|
||||||
cache.set('promotions_access_token', auth_data, timeout=auth_data.get('expires_in') - 5)
|
|
||||||
|
|
||||||
return auth_data
|
|
||||||
|
|
||||||
|
|
||||||
def log_request(request: Request):
|
|
||||||
print(request.headers['authorization'])
|
|
||||||
print(
|
|
||||||
f"Request event hook: {request.method} {request.url} {request.method} {request.headers} - Waiting for response")
|
|
||||||
|
|
||||||
|
|
||||||
def log_response(response):
|
|
||||||
request = response.request
|
|
||||||
print(f"Response event hook: {request.method} {request.url} - Status {response.status_code}")
|
|
||||||
|
|
||||||
|
|
||||||
def get_client():
|
|
||||||
access_token = login_as_client_credentials()['access_token']
|
|
||||||
client = AuthenticatedClient(base_url=settings.ACCOUNTS_BASE_PUBLIC_URL, token=access_token,
|
|
||||||
httpx_args={"event_hooks": {"request": [log_request], "response": [log_response]}}
|
|
||||||
)
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_info(uuid):
|
|
||||||
client = get_client()
|
|
||||||
response: PublicUser = users_api_users_retrieve.sync(client=client, uuid=uuid)
|
|
||||||
print(response)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_detailed_info(uuid):
|
|
||||||
client = get_client()
|
|
||||||
response: Account = users_api_users_details_retrieve.sync(uuid=uuid, client=client)
|
|
||||||
print(response)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def get_application_details(uuid):
|
|
||||||
client = get_client()
|
|
||||||
data: Application = oauth2_apis_applications_retrieve.sync(client=client, id=uuid)
|
|
||||||
print(data)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
"""A client library for accessing Gooyal Accounts"""
|
|
||||||
|
|
||||||
from .client import AuthenticatedClient, Client
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"AuthenticatedClient",
|
|
||||||
"Client",
|
|
||||||
)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains endpoint functions for accessing the API"""
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.application import Application
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
id: str,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/oauth2/apis/applications/{id}/avatar/",
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Application]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = Application.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Application]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Application]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
id=id,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Application
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
id=id,
|
|
||||||
client=client,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Application]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
id=id,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Application
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
id=id,
|
|
||||||
client=client,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.application import Application
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
id: str,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/oauth2/apis/applications/{id}/",
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Application]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = Application.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Application]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Application]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
id=id,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Application
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
id=id,
|
|
||||||
client=client,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Application]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
id=id,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
id: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Application]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
id (str):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Application
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
id=id,
|
|
||||||
client=client,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains endpoint functions for accessing the API"""
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.account import Account
|
|
||||||
from ...models.patched_account import PatchedAccount
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: PatchedAccount,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "patch",
|
|
||||||
"url": "/users/api/account/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Account]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = Account.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Account]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedAccount,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedAccount):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedAccount,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedAccount):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedAccount,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedAccount):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedAccount,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedAccount):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.account import Account
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: Account,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "put",
|
|
||||||
"url": "/users/api/account/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Account]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = Account.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Account]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Account,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (Account):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Account,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (Account):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Account,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (Account):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Account,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (Account):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.change_password import ChangePassword
|
|
||||||
from ...models.patched_change_password import PatchedChangePassword
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: PatchedChangePassword,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "patch",
|
|
||||||
"url": "/users/api/change_password/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Optional[ChangePassword]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = ChangePassword.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Response[ChangePassword]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedChangePassword,
|
|
||||||
) -> Response[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[ChangePassword]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedChangePassword,
|
|
||||||
) -> Optional[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ChangePassword
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedChangePassword,
|
|
||||||
) -> Response[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[ChangePassword]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PatchedChangePassword,
|
|
||||||
) -> Optional[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (PatchedChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ChangePassword
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.change_password import ChangePassword
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: ChangePassword,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "put",
|
|
||||||
"url": "/users/api/change_password/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Optional[ChangePassword]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = ChangePassword.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Response[ChangePassword]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: ChangePassword,
|
|
||||||
) -> Response[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (ChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[ChangePassword]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: ChangePassword,
|
|
||||||
) -> Optional[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (ChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ChangePassword
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: ChangePassword,
|
|
||||||
) -> Response[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (ChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[ChangePassword]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: ChangePassword,
|
|
||||||
) -> Optional[ChangePassword]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (ChangePassword):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ChangePassword
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.user_inquiry import UserInquiry
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "post",
|
|
||||||
"url": "/users/api/inquiry/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[UserInquiry]:
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_201 = UserInquiry.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_201
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[UserInquiry]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Response[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[UserInquiry]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Optional[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
UserInquiry
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Response[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[UserInquiry]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: UserInquiry,
|
|
||||||
) -> Optional[UserInquiry]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (UserInquiry):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
UserInquiry
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.request_otp import RequestOTP
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: RequestOTP,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "post",
|
|
||||||
"url": "/users/api/request_otp/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[RequestOTP]:
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_201 = RequestOTP.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_201
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[RequestOTP]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTP,
|
|
||||||
) -> Response[RequestOTP]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTP):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[RequestOTP]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTP,
|
|
||||||
) -> Optional[RequestOTP]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTP):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
RequestOTP
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTP,
|
|
||||||
) -> Response[RequestOTP]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTP):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[RequestOTP]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTP,
|
|
||||||
) -> Optional[RequestOTP]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTP):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
RequestOTP
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,151 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.request_ott import RequestOTT
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
*,
|
|
||||||
body: RequestOTT,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "post",
|
|
||||||
"url": "/users/api/request_ott/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_body = body.to_dict()
|
|
||||||
|
|
||||||
_kwargs["json"] = _body
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[RequestOTT]:
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_201 = RequestOTT.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_201
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[RequestOTT]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTT,
|
|
||||||
) -> Response[RequestOTT]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTT):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[RequestOTT]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTT,
|
|
||||||
) -> Optional[RequestOTT]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTT):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
RequestOTT
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTT,
|
|
||||||
) -> Response[RequestOTT]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTT):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[RequestOTT]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: RequestOTT,
|
|
||||||
) -> Optional[RequestOTT]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
body (RequestOTT):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
RequestOTT
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
uuid: UUID,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/users/api/users/{uuid}/avatar",
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
return None
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Any]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Any]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Any]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Any]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.account import Account
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
uuid: UUID,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/users/api/users/{uuid}/details",
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Account]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = Account.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Account]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
uuid=uuid,
|
|
||||||
client=client,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Response[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Account]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
uuid: UUID,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
) -> Optional[Account]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
uuid (UUID):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Account
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
uuid=uuid,
|
|
||||||
client=client,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
"""Contains all the data models used in inputs/outputs"""
|
|
||||||
|
|
||||||
from .account import Account
|
|
||||||
from .application import Application
|
|
||||||
from .blank_enum import BlankEnum
|
|
||||||
from .change_password import ChangePassword
|
|
||||||
from .education_degree_enum import EducationDegreeEnum
|
|
||||||
from .gender_enum import GenderEnum
|
|
||||||
from .paginated_application_list import PaginatedApplicationList
|
|
||||||
from .paginated_public_user_list import PaginatedPublicUserList
|
|
||||||
from .patched_account import PatchedAccount
|
|
||||||
from .patched_change_password import PatchedChangePassword
|
|
||||||
from .public_user import PublicUser
|
|
||||||
from .request_otp import RequestOTP
|
|
||||||
from .request_ott import RequestOTT
|
|
||||||
from .user_inquiry import UserInquiry
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"Account",
|
|
||||||
"Application",
|
|
||||||
"BlankEnum",
|
|
||||||
"ChangePassword",
|
|
||||||
"EducationDegreeEnum",
|
|
||||||
"GenderEnum",
|
|
||||||
"PaginatedApplicationList",
|
|
||||||
"PaginatedPublicUserList",
|
|
||||||
"PatchedAccount",
|
|
||||||
"PatchedChangePassword",
|
|
||||||
"PublicUser",
|
|
||||||
"RequestOTP",
|
|
||||||
"RequestOTT",
|
|
||||||
"UserInquiry",
|
|
||||||
)
|
|
||||||
|
|
@ -1,410 +0,0 @@
|
||||||
import datetime
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
from dateutil.parser import isoparse
|
|
||||||
|
|
||||||
from ..models.blank_enum import BlankEnum
|
|
||||||
from ..models.education_degree_enum import EducationDegreeEnum
|
|
||||||
from ..models.gender_enum import GenderEnum
|
|
||||||
from ..types import UNSET, Unset
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="Account")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class Account:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
uuid (UUID):
|
|
||||||
email (str):
|
|
||||||
phone_number (str):
|
|
||||||
iban_verified (Union[None, bool]):
|
|
||||||
province_name (str):
|
|
||||||
city_name (str):
|
|
||||||
username (Union[None, Unset, str]): Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
|
|
||||||
first_name (Union[Unset, str]):
|
|
||||||
last_name (Union[Unset, str]):
|
|
||||||
name (Union[Unset, str]):
|
|
||||||
avatar (Union[None, Unset, str]):
|
|
||||||
gender (Union[BlankEnum, GenderEnum, None, Unset]):
|
|
||||||
education_degree (Union[BlankEnum, EducationDegreeEnum, None, Unset]):
|
|
||||||
education_field (Union[None, Unset, str]):
|
|
||||||
birthdate (Union[None, Unset, datetime.date]):
|
|
||||||
iban (Union[None, Unset, str]):
|
|
||||||
province (Union[None, Unset, int]):
|
|
||||||
city (Union[None, Unset, int]):
|
|
||||||
postal_code (Union[None, Unset, str]):
|
|
||||||
address (Union[None, Unset, str]):
|
|
||||||
"""
|
|
||||||
|
|
||||||
uuid: UUID
|
|
||||||
email: str
|
|
||||||
phone_number: str
|
|
||||||
iban_verified: Union[None, bool]
|
|
||||||
province_name: str
|
|
||||||
city_name: str
|
|
||||||
username: Union[None, Unset, str] = UNSET
|
|
||||||
first_name: Union[Unset, str] = UNSET
|
|
||||||
last_name: Union[Unset, str] = UNSET
|
|
||||||
name: Union[Unset, str] = UNSET
|
|
||||||
avatar: Union[None, Unset, str] = UNSET
|
|
||||||
gender: Union[BlankEnum, GenderEnum, None, Unset] = UNSET
|
|
||||||
education_degree: Union[BlankEnum, EducationDegreeEnum, None, Unset] = UNSET
|
|
||||||
education_field: Union[None, Unset, str] = UNSET
|
|
||||||
birthdate: Union[None, Unset, datetime.date] = UNSET
|
|
||||||
iban: Union[None, Unset, str] = UNSET
|
|
||||||
province: Union[None, Unset, int] = UNSET
|
|
||||||
city: Union[None, Unset, int] = UNSET
|
|
||||||
postal_code: Union[None, Unset, str] = UNSET
|
|
||||||
address: Union[None, Unset, str] = UNSET
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
uuid = str(self.uuid)
|
|
||||||
|
|
||||||
email = self.email
|
|
||||||
|
|
||||||
phone_number = self.phone_number
|
|
||||||
|
|
||||||
iban_verified: Union[None, bool]
|
|
||||||
iban_verified = self.iban_verified
|
|
||||||
|
|
||||||
province_name = self.province_name
|
|
||||||
|
|
||||||
city_name = self.city_name
|
|
||||||
|
|
||||||
username: Union[None, Unset, str]
|
|
||||||
if isinstance(self.username, Unset):
|
|
||||||
username = UNSET
|
|
||||||
else:
|
|
||||||
username = self.username
|
|
||||||
|
|
||||||
first_name = self.first_name
|
|
||||||
|
|
||||||
last_name = self.last_name
|
|
||||||
|
|
||||||
name = self.name
|
|
||||||
|
|
||||||
avatar: Union[None, Unset, str]
|
|
||||||
if isinstance(self.avatar, Unset):
|
|
||||||
avatar = UNSET
|
|
||||||
else:
|
|
||||||
avatar = self.avatar
|
|
||||||
|
|
||||||
gender: Union[None, Unset, str]
|
|
||||||
if isinstance(self.gender, Unset):
|
|
||||||
gender = UNSET
|
|
||||||
elif isinstance(self.gender, GenderEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
elif isinstance(self.gender, BlankEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
else:
|
|
||||||
gender = self.gender
|
|
||||||
|
|
||||||
education_degree: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_degree, Unset):
|
|
||||||
education_degree = UNSET
|
|
||||||
elif isinstance(self.education_degree, EducationDegreeEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
elif isinstance(self.education_degree, BlankEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
else:
|
|
||||||
education_degree = self.education_degree
|
|
||||||
|
|
||||||
education_field: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_field, Unset):
|
|
||||||
education_field = UNSET
|
|
||||||
else:
|
|
||||||
education_field = self.education_field
|
|
||||||
|
|
||||||
birthdate: Union[None, Unset, str]
|
|
||||||
if isinstance(self.birthdate, Unset):
|
|
||||||
birthdate = UNSET
|
|
||||||
elif isinstance(self.birthdate, datetime.date):
|
|
||||||
birthdate = self.birthdate.isoformat()
|
|
||||||
else:
|
|
||||||
birthdate = self.birthdate
|
|
||||||
|
|
||||||
iban: Union[None, Unset, str]
|
|
||||||
if isinstance(self.iban, Unset):
|
|
||||||
iban = UNSET
|
|
||||||
else:
|
|
||||||
iban = self.iban
|
|
||||||
|
|
||||||
province: Union[None, Unset, int]
|
|
||||||
if isinstance(self.province, Unset):
|
|
||||||
province = UNSET
|
|
||||||
else:
|
|
||||||
province = self.province
|
|
||||||
|
|
||||||
city: Union[None, Unset, int]
|
|
||||||
if isinstance(self.city, Unset):
|
|
||||||
city = UNSET
|
|
||||||
else:
|
|
||||||
city = self.city
|
|
||||||
|
|
||||||
postal_code: Union[None, Unset, str]
|
|
||||||
if isinstance(self.postal_code, Unset):
|
|
||||||
postal_code = UNSET
|
|
||||||
else:
|
|
||||||
postal_code = self.postal_code
|
|
||||||
|
|
||||||
address: Union[None, Unset, str]
|
|
||||||
if isinstance(self.address, Unset):
|
|
||||||
address = UNSET
|
|
||||||
else:
|
|
||||||
address = self.address
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"uuid": uuid,
|
|
||||||
"email": email,
|
|
||||||
"phone_number": phone_number,
|
|
||||||
"iban_verified": iban_verified,
|
|
||||||
"province_name": province_name,
|
|
||||||
"city_name": city_name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if username is not UNSET:
|
|
||||||
field_dict["username"] = username
|
|
||||||
if first_name is not UNSET:
|
|
||||||
field_dict["first_name"] = first_name
|
|
||||||
if last_name is not UNSET:
|
|
||||||
field_dict["last_name"] = last_name
|
|
||||||
if name is not UNSET:
|
|
||||||
field_dict["name"] = name
|
|
||||||
if avatar is not UNSET:
|
|
||||||
field_dict["avatar"] = avatar
|
|
||||||
if gender is not UNSET:
|
|
||||||
field_dict["gender"] = gender
|
|
||||||
if education_degree is not UNSET:
|
|
||||||
field_dict["education_degree"] = education_degree
|
|
||||||
if education_field is not UNSET:
|
|
||||||
field_dict["education_field"] = education_field
|
|
||||||
if birthdate is not UNSET:
|
|
||||||
field_dict["birthdate"] = birthdate
|
|
||||||
if iban is not UNSET:
|
|
||||||
field_dict["iban"] = iban
|
|
||||||
if province is not UNSET:
|
|
||||||
field_dict["province"] = province
|
|
||||||
if city is not UNSET:
|
|
||||||
field_dict["city"] = city
|
|
||||||
if postal_code is not UNSET:
|
|
||||||
field_dict["postal_code"] = postal_code
|
|
||||||
if address is not UNSET:
|
|
||||||
field_dict["address"] = address
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
uuid = UUID(d.pop("uuid"))
|
|
||||||
|
|
||||||
email = d.pop("email")
|
|
||||||
|
|
||||||
phone_number = d.pop("phone_number")
|
|
||||||
|
|
||||||
def _parse_iban_verified(data: object) -> Union[None, bool]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return cast(Union[None, bool], data)
|
|
||||||
|
|
||||||
iban_verified = _parse_iban_verified(d.pop("iban_verified"))
|
|
||||||
|
|
||||||
province_name = d.pop("province_name")
|
|
||||||
|
|
||||||
city_name = d.pop("city_name")
|
|
||||||
|
|
||||||
def _parse_username(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
username = _parse_username(d.pop("username", UNSET))
|
|
||||||
|
|
||||||
first_name = d.pop("first_name", UNSET)
|
|
||||||
|
|
||||||
last_name = d.pop("last_name", UNSET)
|
|
||||||
|
|
||||||
name = d.pop("name", UNSET)
|
|
||||||
|
|
||||||
def _parse_avatar(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar", UNSET))
|
|
||||||
|
|
||||||
def _parse_gender(data: object) -> Union[BlankEnum, GenderEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_0 = GenderEnum(data)
|
|
||||||
|
|
||||||
return gender_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return gender_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, GenderEnum, None, Unset], data)
|
|
||||||
|
|
||||||
gender = _parse_gender(d.pop("gender", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_degree(data: object) -> Union[BlankEnum, EducationDegreeEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_0 = EducationDegreeEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, EducationDegreeEnum, None, Unset], data)
|
|
||||||
|
|
||||||
education_degree = _parse_education_degree(d.pop("education_degree", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_field(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
education_field = _parse_education_field(d.pop("education_field", UNSET))
|
|
||||||
|
|
||||||
def _parse_birthdate(data: object) -> Union[None, Unset, datetime.date]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
birthdate_type_0 = isoparse(data).date()
|
|
||||||
|
|
||||||
return birthdate_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[None, Unset, datetime.date], data)
|
|
||||||
|
|
||||||
birthdate = _parse_birthdate(d.pop("birthdate", UNSET))
|
|
||||||
|
|
||||||
def _parse_iban(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
iban = _parse_iban(d.pop("iban", UNSET))
|
|
||||||
|
|
||||||
def _parse_province(data: object) -> Union[None, Unset, int]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, int], data)
|
|
||||||
|
|
||||||
province = _parse_province(d.pop("province", UNSET))
|
|
||||||
|
|
||||||
def _parse_city(data: object) -> Union[None, Unset, int]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, int], data)
|
|
||||||
|
|
||||||
city = _parse_city(d.pop("city", UNSET))
|
|
||||||
|
|
||||||
def _parse_postal_code(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
postal_code = _parse_postal_code(d.pop("postal_code", UNSET))
|
|
||||||
|
|
||||||
def _parse_address(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
address = _parse_address(d.pop("address", UNSET))
|
|
||||||
|
|
||||||
account = cls(
|
|
||||||
uuid=uuid,
|
|
||||||
email=email,
|
|
||||||
phone_number=phone_number,
|
|
||||||
iban_verified=iban_verified,
|
|
||||||
province_name=province_name,
|
|
||||||
city_name=city_name,
|
|
||||||
username=username,
|
|
||||||
first_name=first_name,
|
|
||||||
last_name=last_name,
|
|
||||||
name=name,
|
|
||||||
avatar=avatar,
|
|
||||||
gender=gender,
|
|
||||||
education_degree=education_degree,
|
|
||||||
education_field=education_field,
|
|
||||||
birthdate=birthdate,
|
|
||||||
iban=iban,
|
|
||||||
province=province,
|
|
||||||
city=city,
|
|
||||||
postal_code=postal_code,
|
|
||||||
address=address,
|
|
||||||
)
|
|
||||||
|
|
||||||
account.additional_properties = d
|
|
||||||
return account
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="Application")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class Application:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
uuid (UUID):
|
|
||||||
name (str):
|
|
||||||
avatar (Union[None, str]):
|
|
||||||
client_id (str):
|
|
||||||
user (UUID):
|
|
||||||
allowed_scope (str):
|
|
||||||
"""
|
|
||||||
|
|
||||||
uuid: UUID
|
|
||||||
name: str
|
|
||||||
avatar: Union[None, str]
|
|
||||||
client_id: str
|
|
||||||
user: UUID
|
|
||||||
allowed_scope: str
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
uuid = str(self.uuid)
|
|
||||||
|
|
||||||
name = self.name
|
|
||||||
|
|
||||||
avatar: Union[None, str]
|
|
||||||
avatar = self.avatar
|
|
||||||
|
|
||||||
client_id = self.client_id
|
|
||||||
|
|
||||||
user = str(self.user)
|
|
||||||
|
|
||||||
allowed_scope = self.allowed_scope
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"uuid": uuid,
|
|
||||||
"name": name,
|
|
||||||
"avatar": avatar,
|
|
||||||
"client_id": client_id,
|
|
||||||
"user": user,
|
|
||||||
"allowed_scope": allowed_scope,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
uuid = UUID(d.pop("uuid"))
|
|
||||||
|
|
||||||
name = d.pop("name")
|
|
||||||
|
|
||||||
def _parse_avatar(data: object) -> Union[None, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return cast(Union[None, str], data)
|
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar"))
|
|
||||||
|
|
||||||
client_id = d.pop("client_id")
|
|
||||||
|
|
||||||
user = UUID(d.pop("user"))
|
|
||||||
|
|
||||||
allowed_scope = d.pop("allowed_scope")
|
|
||||||
|
|
||||||
application = cls(
|
|
||||||
uuid=uuid,
|
|
||||||
name=name,
|
|
||||||
avatar=avatar,
|
|
||||||
client_id=client_id,
|
|
||||||
user=user,
|
|
||||||
allowed_scope=allowed_scope,
|
|
||||||
)
|
|
||||||
|
|
||||||
application.additional_properties = d
|
|
||||||
return application
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class BlankEnum(str, Enum):
|
|
||||||
VALUE_0 = ""
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.value)
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
|
|
||||||
from ..types import UNSET, Unset
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="ChangePassword")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class ChangePassword:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
old_password (str):
|
|
||||||
new_password (str):
|
|
||||||
old_password_field (Union[Unset, str]): Default: 'password'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
old_password: str
|
|
||||||
new_password: str
|
|
||||||
old_password_field: Union[Unset, str] = "password"
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
old_password = self.old_password
|
|
||||||
|
|
||||||
new_password = self.new_password
|
|
||||||
|
|
||||||
old_password_field = self.old_password_field
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"old_password": old_password,
|
|
||||||
"new_password": new_password,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if old_password_field is not UNSET:
|
|
||||||
field_dict["old_password_field"] = old_password_field
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
old_password = d.pop("old_password")
|
|
||||||
|
|
||||||
new_password = d.pop("new_password")
|
|
||||||
|
|
||||||
old_password_field = d.pop("old_password_field", UNSET)
|
|
||||||
|
|
||||||
change_password = cls(
|
|
||||||
old_password=old_password,
|
|
||||||
new_password=new_password,
|
|
||||||
old_password_field=old_password_field,
|
|
||||||
)
|
|
||||||
|
|
||||||
change_password.additional_properties = d
|
|
||||||
return change_password
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class EducationDegreeEnum(str, Enum):
|
|
||||||
ASSOCIATE = "associate"
|
|
||||||
BACHELORS = "bachelors"
|
|
||||||
DOCTORATE = "doctorate"
|
|
||||||
HIGH = "high"
|
|
||||||
MASTERS = "masters"
|
|
||||||
MIDDLE = "middle"
|
|
||||||
PRIMARY = "primary"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.value)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class GenderEnum(str, Enum):
|
|
||||||
F = "f"
|
|
||||||
M = "m"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.value)
|
|
||||||
|
|
@ -1,425 +0,0 @@
|
||||||
import datetime
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
from dateutil.parser import isoparse
|
|
||||||
|
|
||||||
from ..models.blank_enum import BlankEnum
|
|
||||||
from ..models.education_degree_enum import EducationDegreeEnum
|
|
||||||
from ..models.gender_enum import GenderEnum
|
|
||||||
from ..types import UNSET, Unset
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="PatchedAccount")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class PatchedAccount:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
uuid (Union[Unset, UUID]):
|
|
||||||
username (Union[None, Unset, str]): Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
|
|
||||||
email (Union[Unset, str]):
|
|
||||||
phone_number (Union[Unset, str]):
|
|
||||||
first_name (Union[Unset, str]):
|
|
||||||
last_name (Union[Unset, str]):
|
|
||||||
name (Union[Unset, str]):
|
|
||||||
avatar (Union[None, Unset, str]):
|
|
||||||
gender (Union[BlankEnum, GenderEnum, None, Unset]):
|
|
||||||
education_degree (Union[BlankEnum, EducationDegreeEnum, None, Unset]):
|
|
||||||
education_field (Union[None, Unset, str]):
|
|
||||||
birthdate (Union[None, Unset, datetime.date]):
|
|
||||||
iban (Union[None, Unset, str]):
|
|
||||||
iban_verified (Union[None, Unset, bool]):
|
|
||||||
province (Union[None, Unset, int]):
|
|
||||||
province_name (Union[Unset, str]):
|
|
||||||
city (Union[None, Unset, int]):
|
|
||||||
city_name (Union[Unset, str]):
|
|
||||||
postal_code (Union[None, Unset, str]):
|
|
||||||
address (Union[None, Unset, str]):
|
|
||||||
"""
|
|
||||||
|
|
||||||
uuid: Union[Unset, UUID] = UNSET
|
|
||||||
username: Union[None, Unset, str] = UNSET
|
|
||||||
email: Union[Unset, str] = UNSET
|
|
||||||
phone_number: Union[Unset, str] = UNSET
|
|
||||||
first_name: Union[Unset, str] = UNSET
|
|
||||||
last_name: Union[Unset, str] = UNSET
|
|
||||||
name: Union[Unset, str] = UNSET
|
|
||||||
avatar: Union[None, Unset, str] = UNSET
|
|
||||||
gender: Union[BlankEnum, GenderEnum, None, Unset] = UNSET
|
|
||||||
education_degree: Union[BlankEnum, EducationDegreeEnum, None, Unset] = UNSET
|
|
||||||
education_field: Union[None, Unset, str] = UNSET
|
|
||||||
birthdate: Union[None, Unset, datetime.date] = UNSET
|
|
||||||
iban: Union[None, Unset, str] = UNSET
|
|
||||||
iban_verified: Union[None, Unset, bool] = UNSET
|
|
||||||
province: Union[None, Unset, int] = UNSET
|
|
||||||
province_name: Union[Unset, str] = UNSET
|
|
||||||
city: Union[None, Unset, int] = UNSET
|
|
||||||
city_name: Union[Unset, str] = UNSET
|
|
||||||
postal_code: Union[None, Unset, str] = UNSET
|
|
||||||
address: Union[None, Unset, str] = UNSET
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
uuid: Union[Unset, str] = UNSET
|
|
||||||
if not isinstance(self.uuid, Unset):
|
|
||||||
uuid = str(self.uuid)
|
|
||||||
|
|
||||||
username: Union[None, Unset, str]
|
|
||||||
if isinstance(self.username, Unset):
|
|
||||||
username = UNSET
|
|
||||||
else:
|
|
||||||
username = self.username
|
|
||||||
|
|
||||||
email = self.email
|
|
||||||
|
|
||||||
phone_number = self.phone_number
|
|
||||||
|
|
||||||
first_name = self.first_name
|
|
||||||
|
|
||||||
last_name = self.last_name
|
|
||||||
|
|
||||||
name = self.name
|
|
||||||
|
|
||||||
avatar: Union[None, Unset, str]
|
|
||||||
if isinstance(self.avatar, Unset):
|
|
||||||
avatar = UNSET
|
|
||||||
else:
|
|
||||||
avatar = self.avatar
|
|
||||||
|
|
||||||
gender: Union[None, Unset, str]
|
|
||||||
if isinstance(self.gender, Unset):
|
|
||||||
gender = UNSET
|
|
||||||
elif isinstance(self.gender, GenderEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
elif isinstance(self.gender, BlankEnum):
|
|
||||||
gender = self.gender.value
|
|
||||||
else:
|
|
||||||
gender = self.gender
|
|
||||||
|
|
||||||
education_degree: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_degree, Unset):
|
|
||||||
education_degree = UNSET
|
|
||||||
elif isinstance(self.education_degree, EducationDegreeEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
elif isinstance(self.education_degree, BlankEnum):
|
|
||||||
education_degree = self.education_degree.value
|
|
||||||
else:
|
|
||||||
education_degree = self.education_degree
|
|
||||||
|
|
||||||
education_field: Union[None, Unset, str]
|
|
||||||
if isinstance(self.education_field, Unset):
|
|
||||||
education_field = UNSET
|
|
||||||
else:
|
|
||||||
education_field = self.education_field
|
|
||||||
|
|
||||||
birthdate: Union[None, Unset, str]
|
|
||||||
if isinstance(self.birthdate, Unset):
|
|
||||||
birthdate = UNSET
|
|
||||||
elif isinstance(self.birthdate, datetime.date):
|
|
||||||
birthdate = self.birthdate.isoformat()
|
|
||||||
else:
|
|
||||||
birthdate = self.birthdate
|
|
||||||
|
|
||||||
iban: Union[None, Unset, str]
|
|
||||||
if isinstance(self.iban, Unset):
|
|
||||||
iban = UNSET
|
|
||||||
else:
|
|
||||||
iban = self.iban
|
|
||||||
|
|
||||||
iban_verified: Union[None, Unset, bool]
|
|
||||||
if isinstance(self.iban_verified, Unset):
|
|
||||||
iban_verified = UNSET
|
|
||||||
else:
|
|
||||||
iban_verified = self.iban_verified
|
|
||||||
|
|
||||||
province: Union[None, Unset, int]
|
|
||||||
if isinstance(self.province, Unset):
|
|
||||||
province = UNSET
|
|
||||||
else:
|
|
||||||
province = self.province
|
|
||||||
|
|
||||||
province_name = self.province_name
|
|
||||||
|
|
||||||
city: Union[None, Unset, int]
|
|
||||||
if isinstance(self.city, Unset):
|
|
||||||
city = UNSET
|
|
||||||
else:
|
|
||||||
city = self.city
|
|
||||||
|
|
||||||
city_name = self.city_name
|
|
||||||
|
|
||||||
postal_code: Union[None, Unset, str]
|
|
||||||
if isinstance(self.postal_code, Unset):
|
|
||||||
postal_code = UNSET
|
|
||||||
else:
|
|
||||||
postal_code = self.postal_code
|
|
||||||
|
|
||||||
address: Union[None, Unset, str]
|
|
||||||
if isinstance(self.address, Unset):
|
|
||||||
address = UNSET
|
|
||||||
else:
|
|
||||||
address = self.address
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update({})
|
|
||||||
if uuid is not UNSET:
|
|
||||||
field_dict["uuid"] = uuid
|
|
||||||
if username is not UNSET:
|
|
||||||
field_dict["username"] = username
|
|
||||||
if email is not UNSET:
|
|
||||||
field_dict["email"] = email
|
|
||||||
if phone_number is not UNSET:
|
|
||||||
field_dict["phone_number"] = phone_number
|
|
||||||
if first_name is not UNSET:
|
|
||||||
field_dict["first_name"] = first_name
|
|
||||||
if last_name is not UNSET:
|
|
||||||
field_dict["last_name"] = last_name
|
|
||||||
if name is not UNSET:
|
|
||||||
field_dict["name"] = name
|
|
||||||
if avatar is not UNSET:
|
|
||||||
field_dict["avatar"] = avatar
|
|
||||||
if gender is not UNSET:
|
|
||||||
field_dict["gender"] = gender
|
|
||||||
if education_degree is not UNSET:
|
|
||||||
field_dict["education_degree"] = education_degree
|
|
||||||
if education_field is not UNSET:
|
|
||||||
field_dict["education_field"] = education_field
|
|
||||||
if birthdate is not UNSET:
|
|
||||||
field_dict["birthdate"] = birthdate
|
|
||||||
if iban is not UNSET:
|
|
||||||
field_dict["iban"] = iban
|
|
||||||
if iban_verified is not UNSET:
|
|
||||||
field_dict["iban_verified"] = iban_verified
|
|
||||||
if province is not UNSET:
|
|
||||||
field_dict["province"] = province
|
|
||||||
if province_name is not UNSET:
|
|
||||||
field_dict["province_name"] = province_name
|
|
||||||
if city is not UNSET:
|
|
||||||
field_dict["city"] = city
|
|
||||||
if city_name is not UNSET:
|
|
||||||
field_dict["city_name"] = city_name
|
|
||||||
if postal_code is not UNSET:
|
|
||||||
field_dict["postal_code"] = postal_code
|
|
||||||
if address is not UNSET:
|
|
||||||
field_dict["address"] = address
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
_uuid = d.pop("uuid", UNSET)
|
|
||||||
uuid: Union[Unset, UUID]
|
|
||||||
if isinstance(_uuid, Unset):
|
|
||||||
uuid = UNSET
|
|
||||||
else:
|
|
||||||
uuid = UUID(_uuid)
|
|
||||||
|
|
||||||
def _parse_username(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
username = _parse_username(d.pop("username", UNSET))
|
|
||||||
|
|
||||||
email = d.pop("email", UNSET)
|
|
||||||
|
|
||||||
phone_number = d.pop("phone_number", UNSET)
|
|
||||||
|
|
||||||
first_name = d.pop("first_name", UNSET)
|
|
||||||
|
|
||||||
last_name = d.pop("last_name", UNSET)
|
|
||||||
|
|
||||||
name = d.pop("name", UNSET)
|
|
||||||
|
|
||||||
def _parse_avatar(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar", UNSET))
|
|
||||||
|
|
||||||
def _parse_gender(data: object) -> Union[BlankEnum, GenderEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_0 = GenderEnum(data)
|
|
||||||
|
|
||||||
return gender_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
gender_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return gender_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, GenderEnum, None, Unset], data)
|
|
||||||
|
|
||||||
gender = _parse_gender(d.pop("gender", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_degree(data: object) -> Union[BlankEnum, EducationDegreeEnum, None, Unset]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_0 = EducationDegreeEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
education_degree_type_1 = BlankEnum(data)
|
|
||||||
|
|
||||||
return education_degree_type_1
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[BlankEnum, EducationDegreeEnum, None, Unset], data)
|
|
||||||
|
|
||||||
education_degree = _parse_education_degree(d.pop("education_degree", UNSET))
|
|
||||||
|
|
||||||
def _parse_education_field(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
education_field = _parse_education_field(d.pop("education_field", UNSET))
|
|
||||||
|
|
||||||
def _parse_birthdate(data: object) -> Union[None, Unset, datetime.date]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
birthdate_type_0 = isoparse(data).date()
|
|
||||||
|
|
||||||
return birthdate_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[None, Unset, datetime.date], data)
|
|
||||||
|
|
||||||
birthdate = _parse_birthdate(d.pop("birthdate", UNSET))
|
|
||||||
|
|
||||||
def _parse_iban(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
iban = _parse_iban(d.pop("iban", UNSET))
|
|
||||||
|
|
||||||
def _parse_iban_verified(data: object) -> Union[None, Unset, bool]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, bool], data)
|
|
||||||
|
|
||||||
iban_verified = _parse_iban_verified(d.pop("iban_verified", UNSET))
|
|
||||||
|
|
||||||
def _parse_province(data: object) -> Union[None, Unset, int]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, int], data)
|
|
||||||
|
|
||||||
province = _parse_province(d.pop("province", UNSET))
|
|
||||||
|
|
||||||
province_name = d.pop("province_name", UNSET)
|
|
||||||
|
|
||||||
def _parse_city(data: object) -> Union[None, Unset, int]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, int], data)
|
|
||||||
|
|
||||||
city = _parse_city(d.pop("city", UNSET))
|
|
||||||
|
|
||||||
city_name = d.pop("city_name", UNSET)
|
|
||||||
|
|
||||||
def _parse_postal_code(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
postal_code = _parse_postal_code(d.pop("postal_code", UNSET))
|
|
||||||
|
|
||||||
def _parse_address(data: object) -> Union[None, Unset, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
if isinstance(data, Unset):
|
|
||||||
return data
|
|
||||||
return cast(Union[None, Unset, str], data)
|
|
||||||
|
|
||||||
address = _parse_address(d.pop("address", UNSET))
|
|
||||||
|
|
||||||
patched_account = cls(
|
|
||||||
uuid=uuid,
|
|
||||||
username=username,
|
|
||||||
email=email,
|
|
||||||
phone_number=phone_number,
|
|
||||||
first_name=first_name,
|
|
||||||
last_name=last_name,
|
|
||||||
name=name,
|
|
||||||
avatar=avatar,
|
|
||||||
gender=gender,
|
|
||||||
education_degree=education_degree,
|
|
||||||
education_field=education_field,
|
|
||||||
birthdate=birthdate,
|
|
||||||
iban=iban,
|
|
||||||
iban_verified=iban_verified,
|
|
||||||
province=province,
|
|
||||||
province_name=province_name,
|
|
||||||
city=city,
|
|
||||||
city_name=city_name,
|
|
||||||
postal_code=postal_code,
|
|
||||||
address=address,
|
|
||||||
)
|
|
||||||
|
|
||||||
patched_account.additional_properties = d
|
|
||||||
return patched_account
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
|
|
||||||
from ..types import UNSET, Unset
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="PatchedChangePassword")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class PatchedChangePassword:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
old_password (Union[Unset, str]):
|
|
||||||
old_password_field (Union[Unset, str]): Default: 'password'.
|
|
||||||
new_password (Union[Unset, str]):
|
|
||||||
"""
|
|
||||||
|
|
||||||
old_password: Union[Unset, str] = UNSET
|
|
||||||
old_password_field: Union[Unset, str] = "password"
|
|
||||||
new_password: Union[Unset, str] = UNSET
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
old_password = self.old_password
|
|
||||||
|
|
||||||
old_password_field = self.old_password_field
|
|
||||||
|
|
||||||
new_password = self.new_password
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update({})
|
|
||||||
if old_password is not UNSET:
|
|
||||||
field_dict["old_password"] = old_password
|
|
||||||
if old_password_field is not UNSET:
|
|
||||||
field_dict["old_password_field"] = old_password_field
|
|
||||||
if new_password is not UNSET:
|
|
||||||
field_dict["new_password"] = new_password
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
old_password = d.pop("old_password", UNSET)
|
|
||||||
|
|
||||||
old_password_field = d.pop("old_password_field", UNSET)
|
|
||||||
|
|
||||||
new_password = d.pop("new_password", UNSET)
|
|
||||||
|
|
||||||
patched_change_password = cls(
|
|
||||||
old_password=old_password,
|
|
||||||
old_password_field=old_password_field,
|
|
||||||
new_password=new_password,
|
|
||||||
)
|
|
||||||
|
|
||||||
patched_change_password.additional_properties = d
|
|
||||||
return patched_change_password
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="PublicUser")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class PublicUser:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
avatar (Union[None, str]):
|
|
||||||
name (str):
|
|
||||||
username (Union[None, str]): Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
|
|
||||||
first_name (str):
|
|
||||||
last_name (str):
|
|
||||||
uuid (UUID):
|
|
||||||
"""
|
|
||||||
|
|
||||||
avatar: Union[None, str]
|
|
||||||
name: str
|
|
||||||
username: Union[None, str]
|
|
||||||
first_name: str
|
|
||||||
last_name: str
|
|
||||||
uuid: UUID
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
avatar: Union[None, str]
|
|
||||||
avatar = self.avatar
|
|
||||||
|
|
||||||
name = self.name
|
|
||||||
|
|
||||||
username: Union[None, str]
|
|
||||||
username = self.username
|
|
||||||
|
|
||||||
first_name = self.first_name
|
|
||||||
|
|
||||||
last_name = self.last_name
|
|
||||||
|
|
||||||
uuid = str(self.uuid)
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"avatar": avatar,
|
|
||||||
"name": name,
|
|
||||||
"username": username,
|
|
||||||
"first_name": first_name,
|
|
||||||
"last_name": last_name,
|
|
||||||
"uuid": uuid,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
|
|
||||||
def _parse_avatar(data: object) -> Union[None, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return cast(Union[None, str], data)
|
|
||||||
|
|
||||||
avatar = _parse_avatar(d.pop("avatar"))
|
|
||||||
|
|
||||||
name = d.pop("name")
|
|
||||||
|
|
||||||
def _parse_username(data: object) -> Union[None, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return cast(Union[None, str], data)
|
|
||||||
|
|
||||||
username = _parse_username(d.pop("username"))
|
|
||||||
|
|
||||||
first_name = d.pop("first_name")
|
|
||||||
|
|
||||||
last_name = d.pop("last_name")
|
|
||||||
|
|
||||||
uuid = UUID(d.pop("uuid"))
|
|
||||||
|
|
||||||
public_user = cls(
|
|
||||||
avatar=avatar,
|
|
||||||
name=name,
|
|
||||||
username=username,
|
|
||||||
first_name=first_name,
|
|
||||||
last_name=last_name,
|
|
||||||
uuid=uuid,
|
|
||||||
)
|
|
||||||
|
|
||||||
public_user.additional_properties = d
|
|
||||||
return public_user
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import datetime
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
from dateutil.parser import isoparse
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="RequestOTP")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class RequestOTP:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
phone_number (str):
|
|
||||||
otp_expire (Union[None, datetime.datetime]):
|
|
||||||
ttl (str):
|
|
||||||
"""
|
|
||||||
|
|
||||||
phone_number: str
|
|
||||||
otp_expire: Union[None, datetime.datetime]
|
|
||||||
ttl: str
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
phone_number = self.phone_number
|
|
||||||
|
|
||||||
otp_expire: Union[None, str]
|
|
||||||
if isinstance(self.otp_expire, datetime.datetime):
|
|
||||||
otp_expire = self.otp_expire.isoformat()
|
|
||||||
else:
|
|
||||||
otp_expire = self.otp_expire
|
|
||||||
|
|
||||||
ttl = self.ttl
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"phone_number": phone_number,
|
|
||||||
"otp_expire": otp_expire,
|
|
||||||
"ttl": ttl,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
phone_number = d.pop("phone_number")
|
|
||||||
|
|
||||||
def _parse_otp_expire(data: object) -> Union[None, datetime.datetime]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
otp_expire_type_0 = isoparse(data)
|
|
||||||
|
|
||||||
return otp_expire_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[None, datetime.datetime], data)
|
|
||||||
|
|
||||||
otp_expire = _parse_otp_expire(d.pop("otp_expire"))
|
|
||||||
|
|
||||||
ttl = d.pop("ttl")
|
|
||||||
|
|
||||||
request_otp = cls(
|
|
||||||
phone_number=phone_number,
|
|
||||||
otp_expire=otp_expire,
|
|
||||||
ttl=ttl,
|
|
||||||
)
|
|
||||||
|
|
||||||
request_otp.additional_properties = d
|
|
||||||
return request_otp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import datetime
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import Any, TypeVar, Union, cast
|
|
||||||
|
|
||||||
from attrs import define as _attrs_define
|
|
||||||
from attrs import field as _attrs_field
|
|
||||||
from dateutil.parser import isoparse
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="RequestOTT")
|
|
||||||
|
|
||||||
|
|
||||||
@_attrs_define
|
|
||||||
class RequestOTT:
|
|
||||||
"""
|
|
||||||
Attributes:
|
|
||||||
application (str):
|
|
||||||
ott (Union[None, str]):
|
|
||||||
ott_expire (Union[None, datetime.datetime]):
|
|
||||||
"""
|
|
||||||
|
|
||||||
application: str
|
|
||||||
ott: Union[None, str]
|
|
||||||
ott_expire: Union[None, datetime.datetime]
|
|
||||||
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
|
||||||
application = self.application
|
|
||||||
|
|
||||||
ott: Union[None, str]
|
|
||||||
ott = self.ott
|
|
||||||
|
|
||||||
ott_expire: Union[None, str]
|
|
||||||
if isinstance(self.ott_expire, datetime.datetime):
|
|
||||||
ott_expire = self.ott_expire.isoformat()
|
|
||||||
else:
|
|
||||||
ott_expire = self.ott_expire
|
|
||||||
|
|
||||||
field_dict: dict[str, Any] = {}
|
|
||||||
field_dict.update(self.additional_properties)
|
|
||||||
field_dict.update(
|
|
||||||
{
|
|
||||||
"application": application,
|
|
||||||
"ott": ott,
|
|
||||||
"ott_expire": ott_expire,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return field_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
||||||
d = dict(src_dict)
|
|
||||||
application = d.pop("application")
|
|
||||||
|
|
||||||
def _parse_ott(data: object) -> Union[None, str]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
return cast(Union[None, str], data)
|
|
||||||
|
|
||||||
ott = _parse_ott(d.pop("ott"))
|
|
||||||
|
|
||||||
def _parse_ott_expire(data: object) -> Union[None, datetime.datetime]:
|
|
||||||
if data is None:
|
|
||||||
return data
|
|
||||||
try:
|
|
||||||
if not isinstance(data, str):
|
|
||||||
raise TypeError()
|
|
||||||
ott_expire_type_0 = isoparse(data)
|
|
||||||
|
|
||||||
return ott_expire_type_0
|
|
||||||
except: # noqa: E722
|
|
||||||
pass
|
|
||||||
return cast(Union[None, datetime.datetime], data)
|
|
||||||
|
|
||||||
ott_expire = _parse_ott_expire(d.pop("ott_expire"))
|
|
||||||
|
|
||||||
request_ott = cls(
|
|
||||||
application=application,
|
|
||||||
ott=ott,
|
|
||||||
ott_expire=ott_expire,
|
|
||||||
)
|
|
||||||
|
|
||||||
request_ott.additional_properties = d
|
|
||||||
return request_ott
|
|
||||||
|
|
||||||
@property
|
|
||||||
def additional_keys(self) -> list[str]:
|
|
||||||
return list(self.additional_properties.keys())
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> Any:
|
|
||||||
return self.additional_properties[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: Any) -> None:
|
|
||||||
self.additional_properties[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key: str) -> None:
|
|
||||||
del self.additional_properties[key]
|
|
||||||
|
|
||||||
def __contains__(self, key: str) -> bool:
|
|
||||||
return key in self.additional_properties
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
"""A client library for accessing Notifications"""
|
|
||||||
|
|
||||||
from .client import AuthenticatedClient, Client
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"AuthenticatedClient",
|
|
||||||
"Client",
|
|
||||||
)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains methods for accessing the API"""
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains endpoint functions for accessing the API"""
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.email import Email
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
body: Email,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "post",
|
|
||||||
"url": f"/email/application/{user_uuid}/application/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_kwargs["json"] = body.to_dict()
|
|
||||||
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Email]:
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_201 = Email.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_201
|
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Email]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Email,
|
|
||||||
) -> Response[Email]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (Email):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Email]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Email,
|
|
||||||
) -> Optional[Email]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (Email):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Email
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Email,
|
|
||||||
) -> Response[Email]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (Email):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[Email]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: Email,
|
|
||||||
) -> Optional[Email]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (Email):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Email
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.paginated_email_list import PaginatedEmailList
|
|
||||||
from ...types import UNSET, Response, Unset
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
params: dict[str, Any] = {}
|
|
||||||
|
|
||||||
params["limit"] = limit
|
|
||||||
|
|
||||||
params["offset"] = offset
|
|
||||||
|
|
||||||
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/email/application/{user_uuid}/application/",
|
|
||||||
"params": params,
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Optional[PaginatedEmailList]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = PaginatedEmailList.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Response[PaginatedEmailList]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Response[PaginatedEmailList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[PaginatedEmailList]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Optional[PaginatedEmailList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PaginatedEmailList
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Response[PaginatedEmailList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[PaginatedEmailList]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Optional[PaginatedEmailList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PaginatedEmailList
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contains endpoint functions for accessing the API"""
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.push_message import PushMessage
|
|
||||||
from ...types import Response
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
body: PushMessage,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
headers: dict[str, Any] = {}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "post",
|
|
||||||
"url": f"/push/application/{user_uuid}/application/",
|
|
||||||
}
|
|
||||||
|
|
||||||
_kwargs["json"] = body.to_dict()
|
|
||||||
|
|
||||||
headers["Content-Type"] = "application/json"
|
|
||||||
|
|
||||||
_kwargs["headers"] = headers
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[PushMessage]:
|
|
||||||
if response.status_code == 201:
|
|
||||||
response_201 = PushMessage.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_201
|
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[PushMessage]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PushMessage,
|
|
||||||
) -> Response[PushMessage]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (PushMessage):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[PushMessage]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PushMessage,
|
|
||||||
) -> Optional[PushMessage]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (PushMessage):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PushMessage
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PushMessage,
|
|
||||||
) -> Response[PushMessage]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (PushMessage):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[PushMessage]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
body: PushMessage,
|
|
||||||
) -> Optional[PushMessage]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
body (PushMessage):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PushMessage
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
body=body,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import Any, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from ... import errors
|
|
||||||
from ...client import AuthenticatedClient, Client
|
|
||||||
from ...models.paginated_push_message_list import PaginatedPushMessageList
|
|
||||||
from ...types import UNSET, Response, Unset
|
|
||||||
|
|
||||||
|
|
||||||
def _get_kwargs(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
params: dict[str, Any] = {}
|
|
||||||
|
|
||||||
params["limit"] = limit
|
|
||||||
|
|
||||||
params["offset"] = offset
|
|
||||||
|
|
||||||
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
|
|
||||||
|
|
||||||
_kwargs: dict[str, Any] = {
|
|
||||||
"method": "get",
|
|
||||||
"url": f"/push/application/{user_uuid}/application/",
|
|
||||||
"params": params,
|
|
||||||
}
|
|
||||||
|
|
||||||
return _kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Optional[PaginatedPushMessageList]:
|
|
||||||
if response.status_code == 200:
|
|
||||||
response_200 = PaginatedPushMessageList.from_dict(response.json())
|
|
||||||
|
|
||||||
return response_200
|
|
||||||
|
|
||||||
if client.raise_on_unexpected_status:
|
|
||||||
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _build_response(
|
|
||||||
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
|
||||||
) -> Response[PaginatedPushMessageList]:
|
|
||||||
return Response(
|
|
||||||
status_code=HTTPStatus(response.status_code),
|
|
||||||
content=response.content,
|
|
||||||
headers=response.headers,
|
|
||||||
parsed=_parse_response(client=client, response=response),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Response[PaginatedPushMessageList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[PaginatedPushMessageList]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = client.get_httpx_client().request(
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
def sync(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Optional[PaginatedPushMessageList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PaginatedPushMessageList
|
|
||||||
"""
|
|
||||||
|
|
||||||
return sync_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
).parsed
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio_detailed(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Response[PaginatedPushMessageList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response[PaginatedPushMessageList]
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = _get_kwargs(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = await client.get_async_httpx_client().request(**kwargs)
|
|
||||||
|
|
||||||
return _build_response(client=client, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
async def asyncio(
|
|
||||||
user_uuid: str,
|
|
||||||
*,
|
|
||||||
client: AuthenticatedClient,
|
|
||||||
limit: Union[Unset, int] = UNSET,
|
|
||||||
offset: Union[Unset, int] = UNSET,
|
|
||||||
) -> Optional[PaginatedPushMessageList]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
user_uuid (str):
|
|
||||||
limit (Union[Unset, int]):
|
|
||||||
offset (Union[Unset, int]):
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
||||||
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PaginatedPushMessageList
|
|
||||||
"""
|
|
||||||
|
|
||||||
return (
|
|
||||||
await asyncio_detailed(
|
|
||||||
user_uuid=user_uuid,
|
|
||||||
client=client,
|
|
||||||
limit=limit,
|
|
||||||
offset=offset,
|
|
||||||
)
|
|
||||||
).parsed
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue