xync-db 0.5.5.dev64__tar.gz → 0.5.5.dev82__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. xync_db-0.5.5.dev82/.gitignore +39 -0
  2. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/PKG-INFO +6 -12
  3. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/README.md +5 -11
  4. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/enums.py +3 -1
  5. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/i18n.py +1 -1
  6. xync_db-0.5.5.dev82/src/xync_db/migrations/CLAUDE.md +21 -0
  7. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/0001_initial.py +2 -2
  8. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/0002_triggers.py +15 -16
  9. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/0003_seed.py +2 -4
  10. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/a_actor_seed.py +1 -1
  11. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/a_bin_seed.py +1 -1
  12. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/a_country_seed.py +2 -6
  13. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/b_htx_seed.py +1 -1
  14. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/models/CLAUDE.md +5 -19
  15. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/models/__init__.py +7 -10
  16. xync_db-0.5.5.dev64/.gitignore +0 -48
  17. xync_db-0.5.5.dev64/src/xync_db/migrations/CLAUDE.md +0 -129
  18. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/pyproject.toml +0 -0
  19. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/__init__.py +0 -0
  20. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/config.py +0 -0
  21. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/exceptions.py +0 -0
  22. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/graph.py +0 -0
  23. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/login_store.py +0 -0
  24. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/logo.png +0 -0
  25. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/__init__.py +0 -0
  26. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/__init__.py +0 -0
  27. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/__init__.py +0 -0
  28. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/a_tg_seed.py +0 -0
  29. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/c_net_seed.py +0 -0
  30. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/countries.json +0 -0
  31. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/ranges.csv +0 -0
  32. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/migrations/models/seed/xync_files.sql +0 -0
  33. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/py.typed +0 -0
  34. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/shared.py +0 -0
  35. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/__init__.py +0 -0
  36. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/__init__.py +0 -0
  37. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/actor.py +0 -0
  38. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/ad.py +0 -0
  39. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/common.py +0 -0
  40. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/cred.py +0 -0
  41. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/order.py +0 -0
  42. {xync_db-0.5.5.dev64 → xync_db-0.5.5.dev82}/src/xync_db/typs/db/pm.py +0 -0
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .env
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+ dist/
14
+ build/
15
+ *.log*
16
+
17
+ # uv
18
+ .uv-cache/
19
+
20
+ # Node
21
+ node_modules/
22
+ *.tsbuildinfo
23
+
24
+ # Env
25
+ .env
26
+ .env.local
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+
33
+ # OS
34
+ .DS_Store
35
+ Thumbs.db
36
+
37
+
38
+ packages/xync-web
39
+ packages/xync-chrome-ext
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: xync-db
3
- Version: 0.5.5.dev64
3
+ Version: 0.5.5.dev82
4
4
  Summary: Xync DB schema: Tortoise ORM models, enums, pydantic DTOs.
5
5
  Author-email: Mike Artemiev <mixartemev@gmail.com>
6
6
  License: LicenseRef-EULA
@@ -56,7 +56,7 @@ Single source of truth for data shape across the monorepo.
56
56
  classDiagram
57
57
  direction BT
58
58
  class Actor {
59
- uint8 exid
59
+ uint16 exid
60
60
  uint1 ex_id
61
61
  varchar(63) name
62
62
  integer person_id
@@ -335,13 +335,7 @@ user --> username : username_id->username.id
335
335
  - а в `Order` соответственно через `ad_id` получаем `ad.actor.ex_id`
336
336
 
337
337
 
338
- ## Migrations (native Tortoise CLI — no aerich)
339
- ```bash
340
- cd packages/xync-db
341
- tortoise init # only once
342
- tortoise makemigrations # detect changes
343
- tortoise migrate # apply
344
- tortoise downgrade models 0001_initial # roll back
345
- tortoise sqlmigrate models 0001_initial # preview SQL
346
- ```
347
- Config lives in this package's `pyproject.toml` under `[tool.tortoise]`.
338
+ ## Миграции
339
+ Ровно три: `0001_initial` (схема), `0002_triggers`, `0003_seed`; изменение схемы — правкой `0001`, новых миграций
340
+ не заводим (`src/xync_db/migrations/CLAUDE.md`). Применить: `tortoise migrate` (конфиг — `[tool.tortoise]` в
341
+ `pyproject.toml`).
@@ -38,7 +38,7 @@ Single source of truth for data shape across the monorepo.
38
38
  classDiagram
39
39
  direction BT
40
40
  class Actor {
41
- uint8 exid
41
+ uint16 exid
42
42
  uint1 ex_id
43
43
  varchar(63) name
44
44
  integer person_id
@@ -317,13 +317,7 @@ user --> username : username_id->username.id
317
317
  - а в `Order` соответственно через `ad_id` получаем `ad.actor.ex_id`
318
318
 
319
319
 
320
- ## Migrations (native Tortoise CLI — no aerich)
321
- ```bash
322
- cd packages/xync-db
323
- tortoise init # only once
324
- tortoise makemigrations # detect changes
325
- tortoise migrate # apply
326
- tortoise downgrade models 0001_initial # roll back
327
- tortoise sqlmigrate models 0001_initial # preview SQL
328
- ```
329
- Config lives in this package's `pyproject.toml` under `[tool.tortoise]`.
320
+ ## Миграции
321
+ Ровно три: `0001_initial` (схема), `0002_triggers`, `0003_seed`; изменение схемы — правкой `0001`, новых миграций
322
+ не заводим (`src/xync_db/migrations/CLAUDE.md`). Применить: `tortoise migrate` (конфиг — `[tool.tortoise]` в
323
+ `pyproject.toml`).
@@ -236,7 +236,9 @@ class CredType(
236
236
  foreign = 3 # продавец просит платёж на зарубежную (не российскую) карту/номер: «перевод на зарубежную карту»
237
237
 
238
238
 
239
- class GeminiModel(IntEnum): # модель Gemini (`GeminiReq`); имя в API, порядок силы и квоты — `cond_ai.MODELS`
239
+ class GeminiModel(
240
+ IntEnum
241
+ ): # модель Gemini (`GeminiReq`); имя в API, порядок силы и квоты — `cond_ai.providers.gemini.MODELS`
240
242
  lite_3_5 = 5
241
243
  lite_3_1 = 6
242
244
 
@@ -1,4 +1,4 @@
1
- """Bilingual text selector for user-facing strings (D-026).
1
+ """Bilingual text selector for user-facing strings (D-08).
2
2
 
3
3
  Usage::
4
4
 
@@ -0,0 +1,21 @@
1
+ # migrations — три миграции (D-09)
2
+
3
+ Конфиг — `xync_service.config.TORTOISE_ORM`; `make migrate` / `make makemigrations` из корня.
4
+
5
+ 1. **`0001_initial`** — полная финальная схема (`ops.CreateModel`). **Любое изменение схемы — правкой этого
6
+ файла**: `0003_seed` пишет через ORM по финальной модели, поздняя миграция с новой колонкой уронит засев.
7
+ Новых миграций не заводим; на прод (ledger фейковый) — DDL руками.
8
+ 2. **`0002_triggers`** — все триггеры и функции (`CREATE OR REPLACE`): NOTIFY-каналы (`contracts/events.md`),
9
+ гарды (`tx_before`, `tx_no_delete`, `ad_exid_ex_uniq`, `ad_cond_side_uniq`, `cond_txt_side_uniq`), GC условий
10
+ (`ad_cond_gc`, `cond_new_gc`), утилиты `_user_balance`, `_tx_pack`. Идёт до засева — сид полагается на
11
+ `user_new`.
12
+ 3. **`0003_seed`** — засев по зависимостям: файлы PM-логотипов → `a_actor_seed` (биржи, 56 базовых валют, монеты,
13
+ XyncPay, эл. кошельки, origin user id=0) → `a_tg_seed` (DC/App, `pm_rep`) → `a_country_seed` (страны+валюты
14
+ из `seed/countries.json`) → `a_bin_seed` (BIN/банки из `seed/ranges.csv`) → `b_htx_seed` (валюты/монеты/пары
15
+ HTX) → `c_net_seed` (web3-сети).
16
+
17
+ Засев, зависящий от Pm (`set_pms` с логотипами, `refine_pms`), — в `xync-script`, не здесь: миграции не
18
+ импортируют `pyro_client`.
19
+
20
+ Дев-БД пересобирать только с нуля (`DROP`/`CREATE` + `make migrate`); таблицы, созданные заранее
21
+ `generate_schemas`, ломают `0001` (DuplicateTable).
@@ -595,7 +595,7 @@ class Migration(migrations.Migration):
595
595
  options={"table": "pm_group", "app": "models", "pk_attr": "id"},
596
596
  bases=["Model"],
597
597
  ),
598
- # `bank` — раньше `Bank`/`Bin`/`Card` (D-038), т.к. на него ссылается `pm.bank_id`
598
+ # `bank` — раньше `Bank`/`Bin`/`Card` (D-17), т.к. на него ссылается `pm.bank_id`
599
599
  ops.CreateModel(
600
600
  name="Bank",
601
601
  fields=[
@@ -2691,7 +2691,7 @@ class Migration(migrations.Migration):
2691
2691
  options={"table": "vpn", "app": "models", "pk_attr": "id", "table_description": "VPNs"},
2692
2692
  bases=["Model"],
2693
2693
  ),
2694
- # ── Cards / BINs (D-038; засев — `0003_seed` → a_bin_seed из binlist/data; `Bank` создан выше — на него ссылается `pm`) ──
2694
+ # ── Cards / BINs (D-17; засев — `0003_seed` → a_bin_seed из binlist/data; `Bank` создан выше — на него ссылается `pm`) ──
2695
2695
  ops.CreateModel(
2696
2696
  name="Bin",
2697
2697
  fields=[
@@ -1,7 +1,7 @@
1
1
  """PG triggers migration — ВСЕ триггеры БД в одном месте.
2
2
 
3
- Определяют NOTIFY-триггеры, которые слушает pg-listener service-api. Раньше часть жила отдельными
4
- миграциями (0005-0009); слиты сюда. `CREATE OR REPLACE` + порядок в `operations` дают финальную форму.
3
+ Определяют NOTIFY-триггеры, которые слушает pg-listener service-api. `CREATE OR REPLACE` + порядок в
4
+ `operations` дают финальную форму.
5
5
 
6
6
  Channels emitted:
7
7
  after_orders_new -- new order, no hot_id
@@ -9,24 +9,23 @@ Channels emitted:
9
9
  hot_order_upd_status -- hot order status change
10
10
  after_orders_upd_status -- non-hot order status change
11
11
  agents_upd_status -- agent status toggle
12
- agents_upd_auth -- agent web-session (cookies/headers) changed (0007)
12
+ agents_upd_auth -- agent web-session (cookies/headers) changed
13
13
  actor_person_needs_def_user -- new actor needs person assignment
14
14
  after_users_new -- new user (also creates person + xync-actor)
15
15
  tx_verified -- transaction reached verified
16
16
  ads_upd / mads_upd -- ad row changed (diff payload)
17
- creds_new/creds_del/creds_upd, credexs_del (0005), credexs_orphan (0006) -- cred/credex lifecycle
18
- w3addr_wanted / w3addr_filled -- web3 address handshake (0009)
17
+ creds_new/creds_del/creds_upd, credexs_del, credexs_orphan -- cred/credex lifecycle
18
+ w3addr_wanted / w3addr_filled -- web3 address handshake
19
19
 
20
20
  Guards (RAISE, без NOTIFY): tx_before (баланс/иммутабельность/монотонность), tx_no_delete,
21
- ad_exid_ex_uniq (0008, exid уникален per-exchange), ad_cond_side_uniq/cond_txt_side_uniq (unique
21
+ ad_exid_ex_uniq (exid уникален per-exchange), ad_cond_side_uniq/cond_txt_side_uniq (unique
22
22
  (cond.raw_txt, ad.pair_side.is_sell)). GC: ad_cond_gc/cond_new_gc — условие без объяв удаляется (cond_gc).
23
23
  Utility: _user_balance(uid, cid), _tx_pack(...).
24
24
 
25
25
  `actor_new` trigger: only the `status = -1` (USER_DEF) branch — an agent person pre-created by
26
26
  `Agent.upsert` triggers the `actor_person_needs_def_user` NOTIFY so an admin is asked whose agent it is.
27
27
  Persons of actors are created ONLY in Python (`Ex.person_save` / `Agent.upsert`, which stamp
28
- `note = "{ex_id}:{exid}"`, D-035); the former auto-person branch was a duplicate path (every
29
- `Actor.create` site passes a person). A person-less actor is a maker masked by the exchange (D-042).
28
+ `note = "{ex_id}:{exid}"`, D-16). A person-less actor is a maker masked by the exchange (D-16).
30
29
  """
31
30
 
32
31
  from typing import ClassVar
@@ -39,8 +38,8 @@ TRIGGERS_SQL = """
39
38
  -- before new Actor
40
39
  CREATE OR REPLACE FUNCTION actor_new() returns trigger as $actor_new_trg$
41
40
  BEGIN
42
- -- персону актору заводит ТОЛЬКО Python (Ex.person_save / Agent.upsert, note = "{ex_id}:{exid}", D-035);
43
- -- актор без person_id — замаскированный биржей мейкер (D-042), персону ему не выдумываем.
41
+ -- персону актору заводит ТОЛЬКО Python (Ex.person_save / Agent.upsert, note = "{ex_id}:{exid}", D-16);
42
+ -- актор без person_id — замаскированный биржей мейкер (D-16), персону ему не выдумываем.
44
43
  -- агентской персоне уже проставлен status=-1 (USER_DEF) в Agent.upsert → зовём админа
45
44
  -- определить юзера (actor_person_needs_def_user).
46
45
  IF (SELECT status FROM person WHERE id = NEW.person_id) = -1 THEN
@@ -262,12 +261,12 @@ CREATE OR REPLACE TRIGGER cred_del BEFORE DELETE ON "cred" FOR EACH ROW EXECUTE
262
261
  """
263
262
 
264
263
  # ─────────────────────────────────────────────────────────────────────────────────────────────────
265
- # Дополнительные триггеры (были отдельными миграциями 0005-0009; здесь слиты). Все `CREATE OR REPLACE`,
264
+ # Дополнительные триггеры. Все `CREATE OR REPLACE`,
266
265
  # поэтому порядок в `operations` гарантирует финальную форму: `credex_del` переопределяет дефолтный
267
266
  # `plsql("credex")` DEL, а `agent_upd` — базовый выше.
268
267
  # ─────────────────────────────────────────────────────────────────────────────────────────────────
269
268
 
270
- # Per-CredEx delete → `credexs_del` (0005). Юзер отвязывает ОДИН кред от ОДНОЙ биржи. Дефолтный
269
+ # Per-CredEx delete → `credexs_del`. Юзер отвязывает ОДИН кред от ОДНОЙ биржи. Дефолтный
271
270
  # `plsql("credex")` DEL нёс лишь OLD.id — бесполезно после каскада. Шлём [person_id, ex_id, exid]
272
271
  # (person через ещё живой родительский `cred`). ПРОПУСКАЕМ notify если родитель-cred уже удалён:
273
272
  # это каскад Cred → BEFORE-DELETE `cred_del()` уже снял каждый credex в `creds_del` (иначе двойное
@@ -288,7 +287,7 @@ $credex_del_trg$ LANGUAGE plpgsql;
288
287
  CREATE OR REPLACE TRIGGER credex_del AFTER DELETE ON "credex" FOR EACH ROW EXECUTE FUNCTION credex_del();
289
288
  """
290
289
 
291
- # Per-CredEx exid-overwrite → `credexs_orphan` (0006). `CredEx.upsert` ключуется по (cred, ex) и
290
+ # Per-CredEx exid-overwrite → `credexs_orphan`. `CredEx.upsert` ключуется по (cred, ex) и
292
291
  # перезаписывает `exid`. Перезапись РЕАЛЬНОГО (не 0) exid другим значением осиротит прежний кред на
293
292
  # бирже (БД его больше не трекает) — не утекаем молча: BEFORE-UPDATE шлёт [person_id, ex_id, OLD.exid,
294
293
  # OLD.cred_id], чтобы service-api спросил владельца, удалить ли устаревший (mirror `is_you`).
@@ -311,7 +310,7 @@ $credex_orphan_trg$ LANGUAGE plpgsql;
311
310
  CREATE OR REPLACE TRIGGER credex_orphan BEFORE UPDATE ON "credex" FOR EACH ROW EXECUTE FUNCTION credex_orphan();
312
311
  """
313
312
 
314
- # Agent.auth change → `agents_upd_auth` (0007). ПЕРЕОПРЕДЕЛЯЕТ `agent_upd` выше: второй, независимый
313
+ # Agent.auth change → `agents_upd_auth`. ПЕРЕОПРЕДЕЛЯЕТ `agent_upd` выше: второй, независимый
315
314
  # branch шлёт `agents_upd_auth` ТОЛЬКО при реальной смене auth (`OLD.auth IS DISTINCT FROM NEW.auth`) —
316
315
  # service-api патчит куки/хедеры в ЖИВУЮ http-сессию агента (`BaseAgentClient.reload_auth`) без рестарта.
317
316
  # Независим от status-branch (status и auth меняются вместе или порознь).
@@ -330,7 +329,7 @@ $agent_upd_trg$ LANGUAGE plpgsql;
330
329
  CREATE OR REPLACE TRIGGER agent_upd AFTER UPDATE ON "agent" FOR EACH ROW EXECUTE FUNCTION agent_upd();
331
330
  """
332
331
 
333
- # Ad.exid uniqueness per-EXCHANGE, не per-maker (0008). `unique_together(exid, maker)` не ловит РАЗНОГО
332
+ # Ad.exid uniqueness per-EXCHANGE, не per-maker. `unique_together(exid, maker)` не ловит РАЗНОГО
334
333
  # maker на ТОЙ ЖЕ бирже с чужим exid (биржа выдаёт ad-id глобально по аккаунтам). Tortoise не выразит
335
334
  # uniq через join (`maker`→`actor.ex_id` не колонка `ad`) → BEFORE INSERT/UPDATE RAISE на конфликт по
336
335
  # (exid≠0, тот же actor.ex_id). Падает громко на записи (как fail-loud в `Ex.e2x_ad`).
@@ -451,7 +450,7 @@ CREATE CONSTRAINT TRIGGER cond_new_gc AFTER INSERT ON "cond" DEFERRABLE INITIALL
451
450
  FOR EACH ROW EXECUTE FUNCTION cond_gc();
452
451
  """
453
452
 
454
- # web3-хендшейк адресов (0009): net_addr_ins (вставка пустого val → зовём получателя завести адрес,
453
+ # web3-хендшейк адресов: net_addr_ins (вставка пустого val → зовём получателя завести адрес,
455
454
  # канал `w3addr_wanted`) и net_addr_upd (val NULL→real → сообщаем отправителям pending-W3Send, канал
456
455
  # `w3addr_filled`). (`ALTER net_addr.val DROP NOT NULL` уехал в 0001 — теперь val создаётся nullable.)
457
456
  NET_ADDR_TRIGGERS_SQL = """
@@ -1,8 +1,6 @@
1
1
  """Полный засев БД одной миграцией — файлы (PM-логотипы) + справочники/сущности в нужном порядке.
2
2
 
3
- Слияние прежних `0003_files_seed` и `0004_all_seed`. Идёт ПОСЛЕ триггеров (0002) и по ПОЛНОЙ схеме
4
- (0001), поэтому сидеры, пишущие через ORM (напр. `set_pairs` → `pair.scale`), не спотыкаются о
5
- недостающие колонки (раньше `pair.scale`/`country.ccn3` добавлялись миграциями ПОСЛЕ засева).
3
+ Идёт ПОСЛЕ триггеров (0002) и по ПОЛНОЙ схеме (0001) — сидеры пишут через ORM по финальной модели.
6
4
 
7
5
  Порядок (зависимости):
8
6
  1. files — PM-логотипы из `seed/xync_files.sql` (если файлов < 100)
@@ -13,7 +11,7 @@
13
11
  6. b_htx_seed — HTX-специфика: валюты (CurEx), монеты, пары
14
12
  7. c_net_seed — web3-сети (Net) + недостающие нативные монеты
15
13
 
16
- NB (D-017): PM-зависимый засев (`set_pms` + `refine_pms`) живёт в `packages/xync-script`, НЕ здесь —
14
+ NB (D-09): PM-зависимый засев (`set_pms` + `refine_pms`) живёт в `packages/xync-script`, НЕ здесь —
17
15
  он тянет `pyro_client` (FileClient), а миграции не должны импортировать `pyro_client`.
18
16
  """
19
17
 
@@ -285,7 +285,7 @@ async def actor_seed(db: BaseDBAsyncClient) -> str:
285
285
  xyncpay, _ = await Pm.update_or_create(dict(ok=True, qr=True, fee=0, typ=PmType.emoney), id=0, norm="xyncpay")
286
286
 
287
287
  # эл. кошельки: в p2p-условиях RUB их называют наравне с банками → свой Bank (Pm с банком попадает в список
288
- # банков разбора условий, `CondAi._pms`) + PmCur RUB; ЦУПИС биржи не отдают — Pm заводим сами. Pyypl/Alipay
288
+ # банков разбора условий, `cond_ai.banks`) + PmCur RUB; ЦУПИС биржи не отдают — Pm заводим сами. Pyypl/Alipay
289
289
  # не рублёвые — df_cur им не ставим (Alipay — CNY в `refine_pms`)
290
290
  rub = await Cur.get(ticker="RUB")
291
291
  wallets = ("yoomoney", None, rub), ("cupis", "цупис", rub), ("pyypl", None, None), ("alipay", None, None)
@@ -5,7 +5,7 @@
5
5
  bank_logo,bank_url,bank_phone,bank_city`. `iin_start` — BIN-префикс (6 ИЛИ 8 цифр), `iin_end` —
6
6
  верх диапазона (пусто = одиночный). Диапазоны маленькие (макс. span 18), поэтому РАЗВОРАЧИВАЕМ их в
7
7
  отдельные префиксы — модель `Bin` остаётся префиксной (`Bin.for_card` ищет 6- и 8-значный префикс,
8
- `Card.from_number` режет тело по длине найденного BIN). См. D-038.
8
+ `Card.from_number` режет тело по длине найденного BIN). См. D-17.
9
9
 
10
10
  Идём после стран, т.к. `Bin.country` линкуется на уже засеянные `Country` по alpha-2 (`short`).
11
11
  Банки дедуплицируем по `name` (первый вариант деталей выигрывает). Идемпотентно: guard по count +
@@ -1,13 +1,9 @@
1
1
  """Единый сидер стран и их валют — ПЕРЕД биржевыми сидерами (`b_htx_seed`).
2
2
 
3
3
  Данные — статический `countries.json` рядом (сгенерирован из mledoze/countries: `name.common`
4
- → name, `cca2` → short, `ccn3` → ccn3, первая из `currencies` → cur). Раньше страны тянулись
5
- из HTX (`cl.countries()`) с ручными костылями (`cnt_codes` для пропавших short, `cur_map`,
6
- `additional_countries`) и разъезжались с реальностью: у 78 стран стояла USD-заглушка (HTX
7
- дефолтил `currencyId=1`), у Бенина short был `"BJ,"` с запятой. Теперь канонично:
4
+ → name, `cca2` → short, `ccn3` → ccn3, первая из `currencies` → cur):
8
5
 
9
- • `ccn3` — числовой ISO 3166-1 (бывшая колонка `code`, где лежал телефонный код — он нигде
10
- не читался, см. миграцию 0012);
6
+ • `ccn3` — числовой ISO 3166-1;
11
7
  • `short` — чистый ISO alpha-2;
12
8
  • `name` — каноническое common-имя;
13
9
  • `cur` — реальная валюта страны. Валюты, которых нет среди 56 базовых (`a_actor_seed`),
@@ -14,7 +14,7 @@ async def htx_seed(db: BaseDBAsyncClient) -> str:
14
14
  await cl.set_curs()
15
15
 
16
16
  # NB: set_pms() lives in packages/xync-script now (it uploads logos via service-pyro and
17
- # so must not pull pyro_client into xync-db — D-017). The script also re-runs the setters
17
+ # so must not pull pyro_client into xync-db — D-09). The script also re-runs the setters
18
18
  # below when an exchange is under-seeded, so they could move there too later.
19
19
  await cl.set_coins()
20
20
  await cl.set_pairs()
@@ -1,8 +1,4 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Overview
1
+ # xync_db.models
6
2
 
7
3
  All Tortoise ORM models (~60) are defined in `__init__.py`. Models are organized into domain groups listed below.
8
4
 
@@ -51,18 +47,8 @@ Re-exported models from `x_auth`: `Username`, `Proxy`, `Dc`, `Fcm`, `App`, `Sess
51
47
  - `Addr` = crypto deposit address `(coin_id, actor_id)` unique; linked to `Net`s via `NetAddr`
52
48
  - `Asset` = balance snapshot `(addr_id, typ)` unique; amounts `free`/`freeze`/`lock`/`target`. Owner reached via `addr → actor` (agent 1:1 actor — the old `agent_id` was redundant with `addr.actor` and was dropped)
53
49
 
54
- actor.set_user(username_id):
55
- При создании юзера(User) в sql-триггере автоматически создается персона(Person) с его user_id.
56
- А при создании актора(Actor) создается персона без user_id, но в actor.person_id = id этой персоны.
57
- И когда юзер заходит в объявление одного из агентов(Agent) первый раз, создается его актор для биржи (на которой
58
- размещено это объявление) и новая персона этого актора.
59
- Если к персоне юзера еще не "привязано" ни одного актора, 2 персоны для юзера с одним актором избыточны, их нужно
60
- смерджить: т.е старой персоне юзера назначить имя, креды(creds) и актора этой новой персоны, а новую "осиротевшую"
61
- персону удалить.
62
- Далее, если этот же юзер будет заходить с других акторов:
63
- - других бирж, но с тем же именем: это та же персона нужно так же смерджить новую персону нового актора с существующей.
64
- - с другим именем: значит это уже другая персона, и существующая персона(ы) уже "занята" существующим(и) актором(ами),
65
- тогда уже не надо мержить новую персону нового актора, оставляем новую.
50
+ Персона актора — только из Python по ключу `note = "{ex_id}:{exid}"`, замаскированный мейкер без персоны;
51
+ слияние — `Person.merge` / `Actor.set_user` (D-16).
66
52
 
67
53
  ### Payment Methods
68
54
  `PmGroup` -> `Pm` -> `PmCur` <- `Cur`, `PmEx` <- `Ex`, `PmExBank`
@@ -88,12 +74,12 @@ actor.set_user(username_id):
88
74
  ### Conditions (Ad parsing)
89
75
  `Cond` -> `CondParsed`, `Synonym`, `CondSim`, `CondPrompt`
90
76
 
91
- - `Cond.raw_txt` = original ad conditions text (4095 chars max, unique)
77
+ - `Cond.raw_txt` = текст условия (до 4095); условие — одной стороны, без объяв не живёт (D-18)
92
78
  - `CondParsed` = structured parse result: `from_party` / `to_party` (`FromParty`: с чьего / на чей счёт идёт платёж
93
79
  покупателя), `ppo`, `slip` (`Slip`) — требует/шлёт понятно из направления объявы, abuse flags, `is_intermediary`
94
80
  (мейкер-посредник «треугол»), linked bank `Pm`s
95
81
  — все поля nullable, дефолт `None` (= «в условии не сказано»; никаких `*.no`-заглушек). Единый writer
96
- `CondParsed.upsert(cond_id, bank_ids=None, **fields)` (бот-разметка /cond и Gemini, D-043); ответ админа
82
+ `CondParsed.upsert(cond_id, bank_ids=None, **fields)` (бот-разметка /cond и разбор нейронками, D-19); ответ админа
97
83
  на «сохранить разбор?» — эмиттер `CondParsed.answer(cond_id, remark=None)` → pg-канал `cond_ai_answer`
98
84
  `[cond_id, remark]` (null — «да», текст — «нет» с замечанием, "" — «нет», замечание пишется, true — «🔄»: объяву
99
85
  условия заново с биржи — реальный exid замаскированного мейкера + свежий `raw_txt`, затем вопрос заново); возвращает, слушает
@@ -404,12 +404,9 @@ class Person(Model, TsTrait):
404
404
  status: PersonStatus = IntEnumField(PersonStatus, db_default=PersonStatus.DEFAULT)
405
405
  name: str | None = CharField(127, null=True)
406
406
  # bootstrap-персона (создана от актора) несёт note = "{ex_id}:{exid}" её актора-источника —
407
- # уникальный ключ, по которому её резолвят Ex.person_save и Agent.upsert (D-035, single-writer).
407
+ # уникальный ключ, по которому её резолвят Ex.person_save и Agent.upsert (D-16).
408
408
  note: str | None = CharField(255, null=True)
409
- # Уникальность на уровне БД (migration 0010_person_note_unique, D-035):
410
- # UNIQUE(note) WHERE note IS NOT NULL — bootstrap-персона уникальна по своему актору;
411
- # UNIQUE NULLS NOT DISTINCT (name, user_id, note) — тёзки живут лишь при разных user_id / note
412
- # (двух (name, NULL, NULL) быть не может).
409
+ # Уникальность в БД — пока только unique(name, user, note); UNIQUE(note) — в decisions.md `## Pending`.
413
410
  user: ForeignKeyNullableRelation[User] = ForeignKeyField("models.User", "persons", on_update=CASCADE, null=True)
414
411
  user_id: int | None
415
412
  creds: BackwardFKRelation[Cred]
@@ -1087,7 +1084,7 @@ class Forum(Model, TsTrait):
1087
1084
 
1088
1085
  class Actor(Model):
1089
1086
  # реальные id бирж влезают в uint8; >= MASKED — id, замаскированный биржей (Bybit userMaskId, 128 бит):
1090
- # публичный листинг объяв прячет мейкера, реальный exid добудем отдельно (D-042)
1087
+ # публичный листинг объяв прячет мейкера, реальный exid добудем отдельно (D-16)
1091
1088
  MASKED: ClassVar[int] = 1 << 64
1092
1089
  exid: int = UInt16Field()
1093
1090
  name: str = CharField(63)
@@ -1269,7 +1266,7 @@ class Agent(Model, TsTrait):
1269
1266
 
1270
1267
  Bootstrap-персону нового актора резолвим
1271
1268
  ПО `note` (`"{ex_id}:{exid}"`, уникальному по актору) — тем же ключом, что клиентский
1272
- `Ex.person_save`, чтобы у Person был ОДИН путь создания (D-035). По НЕуникальному `name` НЕ
1269
+ `Ex.person_save`, чтобы у Person был ОДИН путь создания (D-16). По НЕуникальному `name` НЕ
1273
1270
  ищем: именно `get_or_none(name=rname)` ронял прод `MultipleObjectsReturned` на тёзках без юзера.
1274
1271
 
1275
1272
  Если note не нашёлся (актор впервые) — пробуем gmail-автолинк: замаскированная биржей почта,
@@ -2128,7 +2125,7 @@ class CredEx(Model):
2128
2125
  async def remove(cls, *, cred_id: int, ex_id: int) -> bool:
2129
2126
  """ЕДИНСТВЕННЫЙ standalone-делитор CredEx (single-writer-правило): сносит строку (cred, ex).
2130
2127
  Снятие креда с ОДНОЙ биржи из формы — удаление на бирже реактивно: BEFORE/AFTER-DELETE триггер
2131
- `credex_del` (миграция 0005) положит `[person_id, ex_id, exid]` в `credexs_del` → handler вызовет
2128
+ `credex_del` (0002_triggers) положит `[person_id, ex_id, exid]` в `credexs_del` → handler вызовет
2132
2129
  `acl.cred_del(exid)`. Каскадное удаление при сносе самого Cred идёт другим путём (`creds_del`)."""
2133
2130
  return bool(await cls.filter(cred_id=cred_id, ex_id=ex_id).delete())
2134
2131
 
@@ -2136,7 +2133,7 @@ class CredEx(Model):
2136
2133
  async def orphan_del(cls, *, person_id: int, ex_id: int, exid: int) -> None:
2137
2134
  """Удалить ОСИРОТЕВШИЙ exid на бирже — после согласия владельца (web-контроллер / бот-колбэк).
2138
2135
  Перезапись exid у строки (cred, ex) на другой оставляет старый реквизит висеть на бирже дубликатом;
2139
- BEFORE-UPDATE триггер `credex_orphan` (миграция 0006) ловит это и шлёт `credexs_orphan` → handler
2136
+ BEFORE-UPDATE триггер `credex_orphan` (0002_triggers) ловит это и шлёт `credexs_orphan` → handler
2140
2137
  спрашивает владельца. На «да» зовут СЮДА: свой канал `credexs_orphan_del` (НЕ переиспользуем
2141
2138
  `credexs_del`) — его handler `credex_orphan_del` делает `acl.cred_del(exid)` по (person, ex) И
2142
2139
  возвращает владельцу результат (успех/ошибка) тем же Notify; у обычного `credex_del` отчёта нет,
@@ -2227,7 +2224,7 @@ class Bin(Model):
2227
2224
  @classmethod
2228
2225
  async def for_card(cls, digits: str) -> Bin:
2229
2226
  """Найти BIN для номера карты. BIN бывает 6- или 8-значным, заранее не знаем — пробуем ОБА
2230
- префикса в НАШЕЙ таблице (засеяна из binlist/data, D-038) и берём более специфичный (8 > 6).
2227
+ префикса в НАШЕЙ таблице (засеяна из binlist/data, D-17) и берём более специфичный (8 > 6).
2231
2228
  В binlist.net ходим ТОЛЬКО если не нашли у себя (`_fetch`, префикс 8 цифр)."""
2232
2229
  # префиксы разной длины не пересекаются как int (6-значный < 10^6 ≤ 8-значный), поэтому один
2233
2230
  # запрос prefix__in корректен; order_by(-length) отдаёт 8-значный, если он есть.
@@ -1,48 +0,0 @@
1
- # Python
2
- __pycache__/
3
- *.py[cod]
4
- *.egg-info/
5
- .env
6
- .venv/
7
- venv/
8
- .pytest_cache/
9
- .mypy_cache/
10
- .ruff_cache/
11
- .coverage
12
- htmlcov/
13
- dist/
14
- build/
15
- *.log*
16
-
17
- # uv
18
- .uv-cache/
19
-
20
- # Node
21
- node_modules/
22
- packages/xync-coord/dist/
23
- packages/xync-coord/.vite/
24
- *.tsbuildinfo
25
-
26
- # Env
27
- .env
28
- .env.local
29
-
30
- # IDE
31
- .vscode/
32
- .idea/
33
- *.swp
34
-
35
- # OS
36
- .DS_Store
37
- Thumbs.db
38
-
39
- # Generated
40
- # packages/xync-coord/src/api/schema.gen.ts is regenerated by `npm run codegen`
41
- # (against the live API /schema/openapi.json), but stays tracked so `tsc -b`
42
- # and CI work without a running API.
43
-
44
- # Claude state (board) — track this in git, but ignore live edits if user prefers:
45
- # .claude/state/log.md # uncomment to ignore the journal
46
-
47
- packages/xync-web
48
- packages/xync-chrome-ext
@@ -1,129 +0,0 @@
1
- # migrations/ — Database Migrations (Tortoise native)
2
-
3
- Native Tortoise ORM migrations. Config: `xync_service.config.TORTOISE_ORM`
4
- (referenced from `packages/xync-db/pyproject.toml`).
5
-
6
- CLI (from `packages/xync-db/`): `uv run tortoise makemigrations` / `uv run tortoise migrate`
7
- (or `make makemigrations` / `make migrate` from the repo root).
8
-
9
- > **Консолидировано в 3 миграции (D-037).** Прежние 0001-0012 слиты в:
10
- > 1. **`0001_initial`** — ФИНАЛЬНАЯ схема всех таблиц (`ops.CreateModel`), включая всё, что раньше
11
- > доливали поздние миграции: `pair.scale`+`rate` INT (было 0011), `country.ccn3` (было `code`, 0012),
12
- > `updatestate` перестроен (0010), `net_addr.val` nullable + таблицы `coin_net`/`w3_send` (0009).
13
- > 2. **`0002_triggers`** — ВСЕ триггеры (база + credex_del/credex_orphan/agent_upd-auth/ad_exid_ex_uniq/
14
- > net_addr — были 0005-0009). `CREATE OR REPLACE` + порядок в `operations` дают финальную форму.
15
- > 3. **`0003_seed`** — весь засев (files + actor/tg/country/htx/net) в порядке зависимостей.
16
- >
17
- > **Почему:** сид (0003) пишет через ORM (`set_pairs`→`pair.scale`) и должен идти по ПОЛНОЙ схеме;
18
- > когда колонку добавляла миграция ПОСЛЕ сида, fresh-migrate падал «column … does not exist». Правило:
19
- > **любое изменение схемы для засеваемой таблицы — в `0001`, а не в поздней миграции.**
20
- > Разделы ниже описывают ИСТОРИЧЕСКОЕ содержимое (для контекста) — оно теперь внутри этих трёх файлов.
21
-
22
- ## models/0001_initial.py — Initial Schema
23
-
24
- Creates all ~50 tables. Key tables and their relationships:
25
-
26
- ### Core Domain
27
- - **`user`** — Xync users. `username_id` → `username.id` (Telegram user). Has `pub/prv` (Ed25519 keys for XyncPay signing), `role` (bitmask: READER=4, WRITER=2, MANAGER=6, ADMIN=7), `status` (SLEEP=0, PAY=1, ACTIVE=2)
28
- - **`person`** — Identity abstraction. A user has 1+ persons. `status`: VIP=2, DEFAULT=1, ANONYMOUS=0, USER_DEF=-1, BLOCKED=-2
29
- - **`actor`** — Exchange account. Links `person` to `ex` (exchange). Unique per (ex_id, exid). `exid` = external user id on exchange
30
- - **`agent`** — Automated trading agent. 1:1 with `actor`. Has `auth` (exchange session JSON), `expire_at` (session TTL), `status` (bitmask, bit 1 = listening)
31
-
32
- ### Trading
33
- - **`ad`** — P2P advertisement. Belongs to `maker` (actor), `pair_side` (coin/cur + buy/sell direction). Price/amount stored as integers (scaled)
34
- - **`my_ad`** — Our managed ad (extends `ad` with target_place, shared_at)
35
- - **`order`** — P2P order. Links `ad`, `taker` (actor), `cred` (payment credential), optional `hot` (hot session). Status: created=1, paid=2, completed=3, canceled=4, appealed_by_seller=5, appealed_by_buyer=6, appeal_disputed=7
36
- - **`cred`** — Payment credential (bank account/wallet). `detail` = account number, `ovr_pm_id` = override PM (0 = XyncPay hot cred). Unique per (person, pmcur, ovr_pm_id)
37
-
38
- ### XyncPay
39
- - **`transaction`** — XyncPay transfers. UUID id generated by PG function from packed fields. `status`: request=0, signed=1, verified=2. `proof` = Ed25519 signature
40
- - **`hot`** — Hot swap session. `status`: closed=0, opened=1. Belongs to user
41
- - **`topup`** — Fiat topup to XyncPay balance via external PM
42
-
43
- ### Reference Data
44
- - **`ex`** — Exchanges (Bybit=4, HTX=9, etc). `typ`: p2p=1, cex=2, main=3, dex=4, tg=5. `status`: plan=0, parted=1, full=2
45
- - **`coin`** — Cryptocurrencies with `scale` (decimal places) and `rate` (to base currency)
46
- - **`cur`** — Fiat currencies with `scale` and `rate`
47
- - **`pm`** — Payment methods. `norm` = normalized name, `typ`: bank=0, emoney=1, cash=2, gift_card=3, card=4, IFSC=5, SBP=6, phone=7
48
- - **`pair`/`pair_side`** — Trading pairs (coin+cur) and their buy/sell sides
49
- - **`coinex`/`curex`/`pmex`** — Exchange-specific mappings for coins, currencies, payment methods
50
-
51
- ### Telegram Integration
52
- - **`username`** — Telegram users (id = tg_id)
53
- - **`session`** — Pyrogram sessions for userbot
54
- - **`peer`** — Cached Telegram peers (access_hash)
55
- - **`forum`** — Telegram forum groups per user
56
- - **`dc`** — Telegram data centers
57
- - **`app`** — Telegram API applications
58
-
59
- ### Other
60
- - **`race`/`race_stat`** — Ad price competition automation
61
- - **`cond`/`cond_parsed`/`synonym`** — P2P ad condition text parsing and analysis
62
- - **`fiat`** — Fiat balance per credential
63
- - **`asset`** — Crypto balance per addr (coin+actor) per `typ`; owner via `addr.actor` (no `agent_id`)
64
- - **`transfer`** — Fiat transfer proof for orders
65
- - **`msg`** — Order chat messages
66
-
67
- ## models/0002_triggers.py — PG Triggers and Functions
68
-
69
- ### Entity Triggers
70
- - **`actor_new()`** BEFORE INSERT — auto-creates anonymous `person` if actor has no person_id
71
- - **`user_new()`** AFTER INSERT — auto-creates `person` + `actor` (ex_id=0 = Xync) for new user, notifies `after_users_new`
72
- - **`order_new()`** AFTER INSERT — routes to `hot_order_new` (if hot_id set) or `after_orders_new`
73
- - **`order_upd()`** AFTER UPDATE — routes status changes to `hot_order_new` (hot_id just set), `hot_order_upd_status` (hot status change), or `after_orders_upd_status` (non-hot status change). Payload: `[row_json, old_status]`
74
- - **`agent_upd()`** AFTER UPDATE — notifies `agents_upd_status` on status change. Payload: `[row_json, old_status]`
75
-
76
- ### Transaction Functions
77
- - **`_user_balance(uid, cid)`** — SQL function: calculates XyncPay balance as `SUM(received) - SUM(sent)` with status checks (verified for received, signed+ for sent)
78
- - **`_tx_pack(status, ts, cur_id, receiver_id, amount, sender_id)`** — Generates deterministic UUID from transaction fields: `(type << 30 | ts-epoch) || cur_id[1B] || receiver[3B] || amount[5B] || sender[3B]`
79
- - **`tx_before()`** BEFORE INSERT/UPDATE:
80
- - INSERT: generates id if null, checks sender balance (rejects if insufficient)
81
- - UPDATE: validates that packed fields haven't changed (id must still match generated)
82
- - **`tx_after()`** AFTER INSERT/UPDATE — notifies `tx_verified` when status becomes 2 (verified)
83
- - **`tx_no_delete()`** BEFORE DELETE — prevents transaction deletion entirely
84
-
85
- ### Auto-generated Triggers
86
- - `plsql("cred", 2)` — generates AFTER INSERT trigger for `cred` table → `creds_new` channel
87
- - `plsql("msg", 2)` — generates AFTER INSERT trigger for `msg` table
88
-
89
- ## models/0003_files_seed.py — File Seed
90
-
91
- Loads PM logo files from `seed/xync_files.sql` if < 100 files exist. These are Telegram file references stored as binary `ref` in the `file` table.
92
-
93
- ## models/0004_all_seed.py — Full Seed
94
-
95
- Orchestrates the seed scripts in order:
96
- 1. `a_actor_seed` — Exchanges, currencies (56 base), coins, XyncPay PM, e-wallets yoomoney/cupis/pyypl/alipay (Bank + Pm typ=emoney + RUB pmcur), origin user (id=0)
97
- 2. `a_tg_seed` — Telegram DCs, app config, PM name mappings (`pm_rep`)
98
- 3. `a_country_seed` — **all** countries + their currencies from the bundled `seed/countries.json`
99
- (generated from mledoze/countries), single source, BEFORE the exchange seeders. Sets `ccn3`
100
- (ISO 3166-1 numeric — the old `code`/phone column, renamed in 0012), clean `short` (cca2),
101
- canonical `name`, real `cur` (creates the ~99 non-base `Cur`); `id` baked into the file so
102
- existing countries keep theirs, meta-aggregates hold 201/202. Idempotent. (D-036)
103
- 4. `b_htx_seed` — HTX-specific: currencies (CurEx), coins, pairs (via `xync_client.ExClient`).
104
- No longer seeds countries — those moved to `a_country_seed`.
105
-
106
- > **PM-dependent seeding left the migration (D-017).** PMs aren't populated at migrate
107
- > time (they come from `set_pms`), so anything keyed off them moved to `packages/xync-script`
108
- > (`python -m xync_script`, run after `migrate` with `pyro` up):
109
- > - `b_htx_seed` dropped `set_pms` — it pulled `pyro_client` (FileClient) into xync-db, the
110
- > import cycle (xync-db ⇄ pyro-client) we removed.
111
- > - the former `c_ex_seed` (PM default-currency + bank groups) is now
112
- > `xync_script.seed.refine_pms`, run only when `set_pms` actually added new `Pm` rows.
113
- >
114
- > **Rule:** never import `pyro_client` from a migration. (`b_htx_seed` still uses
115
- > `xync_client.ExClient` for currency/coin/pair seeding — accepted for now.)
116
-
117
- ### Key Seed Data
118
- - 24 exchanges defined (Xync=0, Binance=1, Bybit=4, HTX=9, OKX=14, TgWallet=20...)
119
- - 53 fiat currencies with rates relative to RUB
120
- - 14 coins: XYNC(0), USDT(1), BTC(2), ETH(3), USDC(4), TON(5), SOL(6)...
121
- - Origin user: id=0, username=XyncPay, tg_id=7807393311
122
- - PM name normalizations (`pm_rep`): maps exchange-specific names to canonical forms (e.g. "Tinkoff" → "T-Bank", "ADVCash" → "Volet.com")
123
-
124
- ## models/0005_ad_upd_full_row.py — ad_upd payload
125
-
126
- Replaces the `ad_upd()` trigger function: payload `[NEW.id, diff]` → `[row_to_json(NEW), diff]`
127
- (`diff` = `{field: [old, new]}`, channels `ads_upd`/`mads_upd`). The api handlers build the
128
- exchange push from the row state **at event time** instead of re-reading the (possibly already
129
- overwritten) row from the DB.