migrations
This commit is contained in:
parent
d1b0670d71
commit
f2b07c9f30
57 changed files with 2970 additions and 1 deletions
0
20-dev/00-rust/README.md
Normal file
0
20-dev/00-rust/README.md
Normal file
44
20-dev/00-rust/axum sqlx postgres.md
Normal file
44
20-dev/00-rust/axum sqlx postgres.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
Трейт `Clone` для структуры состояния `AppState` в Axum необходим для обеспечения **потокобезопасного** (thread-safe) доступа к общим данным, таким как пул соединений с базой данных, из нескольких одновременно обрабатываемых запросов.[temofeev+1](https://temofeev.ru/info/articles/nachalo-raboty-s-axum-samym-populyarnym-veb-freymvorkom-rust/)
|
||||
|
||||
Когда вы запускаете веб-сервер на Axum, он обычно работает в многопоточной среде для обработки нескольких запросов параллельно. Каждому запросу (или, точнее, каждому рабочему потоку, который его обрабатывает) требуется свой собственный экземпляр состояния для работы.[rust-lang](https://users.rust-lang.org/t/axum-state-when-using-clone-and-arc/114349)
|
||||
|
||||
## Подробное объяснение
|
||||
|
||||
1. **Расшаривание состояния между потоками**: Axum использует экстрактор `State` для внедрения состояния приложения в обработчики (handlers). Чтобы передать это состояние в разные потоки, которые обрабатывают запросы, Axum должен иметь возможность создавать копии этого состояния. Трейт `Clone` предоставляет стандартизированный способ для такого копирования.[docs+1](https://docs.rs/axum/latest/axum/extract/struct.State.html)
|
||||
|
||||
2. **Эффективность клонирования `PgPool`**: В вашем примере поле `db_pool` имеет тип `PgPool` из библиотеки `sqlx`. Важно понимать, что вызов `.clone()` для `PgPool` — это очень **дешевая операция**. Она не создает новый пул соединений и не открывает новые подключения к базе данных. Вместо этого она создает новый _указатель_ на тот же самый, уже существующий внутренний пул, увеличивая **счетчик ссылок** (reference counter). Это позволяет безопасно и эффективно использовать один и тот же пул соединений в разных частях вашего приложения одновременно.[reddit+1](https://www.reddit.com/r/rust/comments/173zzbb/axum_use_state_without_clone/)
|
||||
|
||||
3. **Внутренняя работа Axum с `Arc`**: Зачастую, если тип состояния реализует `Clone`, метод `.with_state()` в Axum неявно оборачивает ваше состояние в `Arc` (Atomic Reference Counted pointer). `Arc` — это умный указатель, который позволяет безопасно владеть данными из нескольких потоков. Клонирование `Arc` также является дешевой операцией, так как оно лишь увеличивает атомарный счетчик ссылок, не копируя сами данные.[leapcell+2](https://leapcell.io/blog/robust-state-management-in-actix-web-and-axum-applications)
|
||||
|
||||
|
||||
## Практические подходы
|
||||
|
||||
Существует два основных способа управления состоянием в Axum, которые достигают одной и той же цели:
|
||||
|
||||
|Подход|Пример кода|Объяснение|
|
||||
|---|---|---|
|
||||
|**`#[derive(Clone)]` на структуре** (как в вашем примере)|`#[derive(Clone)]` <br>`struct AppState { db_pool: PgPool }` <br>`.with_state(state)`|Это самый простой и распространенный способ. Он работает, потому что `PgPool` и другие подобные "умные" типы уже реализуют `Clone` эффективно (через `Arc` внутри) [temofeev+1](https://temofeev.ru/info/articles/nachalo-raboty-s-axum-samym-populyarnym-veb-freymvorkom-rust/).|
|
||||
|**Явное использование `Arc`**|`type AppState = Arc<MyState>;` <br>`struct MyState { ... }` <br>`.with_state(Arc::new(my_state))`|Этот подход делает разделяемое владение более явным в коде. Вы вручную оборачиваете состояние в `Arc`, а затем клонируете `Arc` при передаче в `.with_state()` [compile7+1](https://compile7.org/implement-data-structures/how-to-implement-stack-in-axum/). Результат тот же, но код более многословен.|
|
||||
|
||||
Таким образом, требование `Clone` — это не о дорогостоящем копировании данных, а о механизме безопасного и эффективного разделения доступа к общему состоянию в асинхронной многопоточной среде Axum. Ваш код с `#[derive(Clone)]` является идиоматичным и правильным для работы с `PgPool` в Axum.[compile7+1](https://compile7.org/implement-data-structures/how-to-implement-stack-in-axum/)
|
||||
|
||||
1. [https://users.rust-lang.org/t/axum-state-when-using-clone-and-arc/114349](https://users.rust-lang.org/t/axum-state-when-using-clone-and-arc/114349)
|
||||
2. [https://docs.rs/axum/latest/axum/extract/struct.State.html](https://docs.rs/axum/latest/axum/extract/struct.State.html)
|
||||
3. [https://www.reddit.com/r/rust/comments/173zzbb/axum_use_state_without_clone/](https://www.reddit.com/r/rust/comments/173zzbb/axum_use_state_without_clone/)
|
||||
4. [https://snix.dev/rustdoc/axum/extract/struct.State.html](https://snix.dev/rustdoc/axum/extract/struct.State.html)
|
||||
5. [https://leapcell.io/blog/robust-state-management-in-actix-web-and-axum-applications](https://leapcell.io/blog/robust-state-management-in-actix-web-and-axum-applications)
|
||||
6. [https://temofeev.ru/info/articles/nachalo-raboty-s-axum-samym-populyarnym-veb-freymvorkom-rust/](https://temofeev.ru/info/articles/nachalo-raboty-s-axum-samym-populyarnym-veb-freymvorkom-rust/)
|
||||
7. [https://compile7.org/implement-data-structures/how-to-implement-stack-in-axum/](https://compile7.org/implement-data-structures/how-to-implement-stack-in-axum/)
|
||||
8. [https://www.test.guide-it.ru/docs/axum/state-middleware/state.html](https://www.test.guide-it.ru/docs/axum/state-middleware/state.html)
|
||||
9. [https://www.shuttle.dev/blog/2023/12/06/using-axum-rust](https://www.shuttle.dev/blog/2023/12/06/using-axum-rust)
|
||||
10. [https://habr.com/ru/articles/770332/](https://habr.com/ru/articles/770332/)
|
||||
11. [https://github.com/tokio-rs/axum/issues/1374](https://github.com/tokio-rs/axum/issues/1374)
|
||||
12. [https://github.com/tokio-rs/axum/discussions/2508](https://github.com/tokio-rs/axum/discussions/2508)
|
||||
13. [https://stackoverflow.com/questions/78855741/is-it-ok-to-clone-a-connection-pool-in-rust-axum](https://stackoverflow.com/questions/78855741/is-it-ok-to-clone-a-connection-pool-in-rust-axum)
|
||||
14. [https://www.reddit.com/r/rust/comments/16kono9/trying_to_use_mutable_appstate_in_axum_and/](https://www.reddit.com/r/rust/comments/16kono9/trying_to_use_mutable_appstate_in_axum_and/)
|
||||
15. [https://docs.rs/axum/latest/axum/](https://docs.rs/axum/latest/axum/)
|
||||
16. [https://users.rust-lang.org/t/how-to-pass-app-state-in-axum/104458](https://users.rust-lang.org/t/how-to-pass-app-state-in-axum/104458)
|
||||
17. [https://qna.habr.com/q/1382956](https://qna.habr.com/q/1382956)
|
||||
18. [https://rust-classes.com/chapter_7_4](https://rust-classes.com/chapter_7_4)
|
||||
19. [https://www.youtube.com/watch?v=XZtlD_m59sM](https://www.youtube.com/watch?v=XZtlD_m59sM)
|
||||
20. [https://tg-rs.github.io/carapax/axum/extract/struct.State.html](https://tg-rs.github.io/carapax/axum/extract/struct.State.html)
|
||||
33
20-dev/00-rust/functional programming.md
Normal file
33
20-dev/00-rust/functional programming.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
Последовательный вызов функций через точку или передача результата одной функции в другую называется по-разному в зависимости от направления и синтаксиса:[habr](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
|
||||
## Основные термины
|
||||
|
||||
**Function composition (композиция функций)** — вызов функций справа налево, где результат одной функции передается в следующую. В математической записи: f∘g∘hf \circ g \circ hf∘g∘h означает f(g(h(x)))f(g(h(x)))f(g(h(x))). В Haskell используется оператор `.` для композиции.[habr](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
|
||||
**Pipeline (конвейер, pipelining)** — передача данных слева направо через последовательность функций. Более естественен для чтения кода, так как порядок совпадает с порядком выполнения. Примеры операторов:[habr](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
|
||||
- `|>` в F#, OCaml, Elixir (pipe operator)
|
||||
|
||||
- `->` и `->>` в Clojure (threading macros)
|
||||
|
||||
- `.then()` в JavaScript Promises
|
||||
|
||||
|
||||
**Method chaining (цепочка методов)** — это ООП-паттерн, когда методы возвращают `self`/`this`, позволяя писать `obj.method1().method2().method3()`. Технически не чисто функциональный подход, так как часто мутирует объект.[habr](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
|
||||
## Различия
|
||||
|
||||
Композиция записывается в обратном порядке выполнения: `compose(three, two, one)(x)` выполнится как `three(two(one(x)))`. Конвейер в прямом: `pipe(one, two, three)(x)` — сначала `one`, потом `two`, потом `three`.[habr](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
|
||||
В Rust методы можно чейнить через точку (например, iterator chains: `vec.iter().map(...).filter(...).collect()`), что семантически близко к pipeline. Это **fluent interface** паттерн, который заимствует идеи из функционального программирования.[habr](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
|
||||
1. [https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5](https://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
|
||||
2. [https://blog.skillfactory.ru/glossary/funkczionalnye-yazyki-programmirovaniya/](https://blog.skillfactory.ru/glossary/funkczionalnye-yazyki-programmirovaniya/)
|
||||
3. [https://habr.com/ru/companies/yandex/articles/547786/](https://habr.com/ru/companies/yandex/articles/547786/)
|
||||
4. [https://it.kgsu.ru/Lisp/lisp0051.html](https://it.kgsu.ru/Lisp/lisp0051.html)
|
||||
5. [https://studfile.net/preview/7802106/page:3/](https://studfile.net/preview/7802106/page:3/)
|
||||
6. [https://gb.ru/blog/funktsionalnoe-programmirovanie/](https://gb.ru/blog/funktsionalnoe-programmirovanie/)
|
||||
7. [https://al.cs.msu.ru/system/files/21-FPL.pdf](https://al.cs.msu.ru/system/files/21-FPL.pdf)
|
||||
8. [https://gitverse.ru/blog/articles/development/168-funkcionalnoe-programmirovanie](https://gitverse.ru/blog/articles/development/168-funkcionalnoe-programmirovanie)
|
||||
9. [https://ipc.susu.ru/fp.html](https://ipc.susu.ru/fp.html)
|
||||
10. [https://www.itweek.ru/themes/detail.php?ID=209957](https://www.itweek.ru/themes/detail.php?ID=209957)
|
||||
63
20-dev/00-rust/microcontrollers rust.md
Normal file
63
20-dev/00-rust/microcontrollers rust.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
https://noxim.xyz/blog/rust-ch32v003/introduction/
|
||||
|
||||
My apologies for the oversight with the `.env` file. Your `justfile` and `sops` setup is a superior, production-ready approach, and I should have recognized that. Excellent work getting the secrets configured and achieving the successful `curl` result! That "abrakadabra" token is the first major milestone.
|
||||
|
||||
So, the big questions: What do we do with this token, and what's next?
|
||||
|
||||
## What to do with the token?
|
||||
|
||||
The token you received is a **Bearer Token**. It's the key that proves to the bank's API that you have permission to access resources on behalf of your application (not a specific user yet, that comes later).
|
||||
|
||||
You will use this token in the `Authorization` header for every subsequent API call to that bank. The `BankClient` is already set up to do this. When you call a method like `get_accounts` (which we will build next), it will first call `get_token()` to get the "abrakadabra" string, and then `reqwest` will attach it like this:
|
||||
|
||||
`Authorization: Bearer abrakadabra`
|
||||
|
||||
This is the standard mechanism for authenticated API requests.
|
||||
|
||||
## Next Steps: A Proposed Roadmap
|
||||
|
||||
Now that we have authentication, we can start building the core features. The ultimate goal is to fetch data, process it, and present it, including generating images of transactions. Here is a logical roadmap to get there. I propose we tackle these one by one, with a consensus check at each stage.
|
||||
|
||||
**Stage 1: Fetching Core Data (The "Read" part)**
|
||||
|
||||
1. **Implement Account & Transaction Models**: Define the `Account` and `Transaction` structs in `src/api/banking.rs` based on the OpenAPI specifications for the hackathon. This gives us the data structures to hold the information we'll fetch.
|
||||
|
||||
2. **Implement `get_accounts()`**: Create a method in `BankClient` to fetch a list of accounts for a specific user. This will be our first real, authenticated API call using the token.
|
||||
|
||||
3. **Create an Axum Handler for Accounts**: Add a new route like `GET /api/accounts/:bank/:user_id` that uses the `get_accounts()` method and returns the data as JSON. This will allow us to test the full flow.
|
||||
|
||||
|
||||
**Stage 2: Storing and Aggregating Data (The "Write" part)**
|
||||
|
||||
1. **Database Schema**: Define the SQL schema for `accounts` and `transactions` in our PostgreSQL database. We'll need to decide what information to store.
|
||||
|
||||
2. **Save to DB**: Modify the Axum handler from Stage 1 to save the fetched accounts and transactions to our database. This is the foundation for creating a unified view of a user's finances.
|
||||
|
||||
|
||||
**Stage 3: Visualization (The "Image Generation" part)**
|
||||
|
||||
1. **Choose an Image Crate**: Research and select a suitable Rust crate for generating images (e.g., `image`, `plotters`, `svg`). `plotters` is a strong contender as it's designed for data visualization and can render to various formats.
|
||||
|
||||
2. **Create an Image Generation Service**: Build a new module (e.g., `src/image_gen.rs`) that takes a list of transactions (fetched from our DB) and generates a visual representation, like a bar chart of spending by category or a timeline.
|
||||
|
||||
3. **Expose via API**: Create a final Axum handler, perhaps `GET /api/transactions/:user_id/image`, that generates and returns the image file.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Let's Start with Stage 1.1
|
||||
|
||||
I propose we begin with the first, most fundamental step: **defining the data models for Accounts and Transactions**. This is low-risk, gets us closer to our goal, and is a prerequisite for everything else.
|
||||
|
||||
Does this roadmap and starting point make sense to you? Once we agree, I can provide the specific code snippets for the data models.
|
||||
|
||||
1. [https://abank.open.bankingapi.ru](https://abank.open.bankingapi.ru)
|
||||
2. [https://www.reddit.com/r/rust/comments/1ia29hp/safest_way_to_store_api_keys_for_production_tauri/](https://www.reddit.com/r/rust/comments/1ia29hp/safest_way_to_store_api_keys_for_production_tauri/)
|
||||
3. [https://www.reddit.com/r/rust/comments/l5b0pq/how_to_manage_api_keys_in_an_open_source_project/](https://www.reddit.com/r/rust/comments/l5b0pq/how_to_manage_api_keys_in_an_open_source_project/)
|
||||
4. [https://github.com/IAmMadfly/rust-up-bank-rest-api](https://github.com/IAmMadfly/rust-up-bank-rest-api)
|
||||
5. [https://stackoverflow.com/questions/57650692/where-to-store-the-refresh-token-on-the-client](https://stackoverflow.com/questions/57650692/where-to-store-the-refresh-token-on-the-client)
|
||||
6. [https://docs.rs/near-contract-standards/latest/near_contract_standards/storage_management/index.html](https://docs.rs/near-contract-standards/latest/near_contract_standards/storage_management/index.html)
|
||||
7. [https://stackoverflow.com/questions/58972571/where-to-securely-store-an-access-token-used-for-api-requests-from-the-monzo-ban](https://stackoverflow.com/questions/58972571/where-to-securely-store-an-access-token-used-for-api-requests-from-the-monzo-ban)
|
||||
8. [https://tms-dev-blog.com/jwt-security-for-a-rust-rest-api/](https://tms-dev-blog.com/jwt-security-for-a-rust-rest-api/)
|
||||
9. [https://auth0.com/blog/build-an-api-in-rust-with-jwt-authentication-using-actix-web/](https://auth0.com/blog/build-an-api-in-rust-with-jwt-authentication-using-actix-web/)
|
||||
10. [https://users.rust-lang.org/t/best-practices-for-managing-authentication-and-sessions-in-rust-web-applications/115884](https://users.rust-lang.org/t/best-practices-for-managing-authentication-and-sessions-in-rust-web-applications/115884)
|
||||
416
20-dev/00-rust/multibank.md
Normal file
416
20-dev/00-rust/multibank.md
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
psd2
|
||||
|
||||
ngrok
|
||||
|
||||
---
|
||||
|
||||
POST
|
||||
/auth/bank-token
|
||||
```bash
|
||||
curl -X 'POST' \
|
||||
'https://vbank.open.bankingapi.ru/auth/bank-token?client_id=team275&client_secret=super6ecretid' \
|
||||
-H 'accept: application/json' \
|
||||
-d ''
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZWFtMjc1IiwiY2xpZW50X2lkIjoidGVhbTI3NSIsInR5cGUiOiJ0ZWFtIiwiaXNzIjoidmJhbmsiLCJhdWQiOiJvcGVuYmFua2luZyIsImV4cCI6MTc2MjUyMjM0MX0.5VNzRnhXnkTA5IkJok-9L-zDGYQSrRmFkGAyw2FM4k0",
|
||||
"token_type": "bearer",
|
||||
"client_id": "team275",
|
||||
"algorithm": "HS256",
|
||||
"expires_in": 86400
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
POST
|
||||
/account-consents/request
|
||||
```bash
|
||||
curl -X 'POST' \
|
||||
'https://vbank.open.bankingapi.ru/account-consents/request' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-requesting-bank: team275' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"client_id": "team275-6",
|
||||
"permissions": [
|
||||
"ReadAccountsDetail",
|
||||
"ReadBalances",
|
||||
"ReadTransactionsDetail"
|
||||
],
|
||||
"reason": "",
|
||||
"requesting_bank": "test_bank",
|
||||
"requesting_bank_name": "Test Bank"
|
||||
}'
|
||||
```
|
||||
json response:
|
||||
```json
|
||||
{
|
||||
"request_id": "req-73fdb8b9d63e",
|
||||
"consent_id": "consent-46965b4ef205",
|
||||
"status": "approved",
|
||||
"message": "Согласие одобрено автоматически",
|
||||
"created_at": "2025-11-06T13:21:06.556824",
|
||||
"auto_approved": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
GET
|
||||
/account-consents/{consent_id}
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://vbank.open.bankingapi.ru/account-consents/consent-46965b4ef205' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-fapi-interaction-id: team275-6'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"consentId": "consent-46965b4ef205",
|
||||
"status": "Authorized",
|
||||
"creationDateTime": "2025-11-06T13:21:06.557680Z",
|
||||
"statusUpdateDateTime": "2025-11-06T13:21:06.557680Z",
|
||||
"permissions": [
|
||||
"ReadAccountsDetail",
|
||||
"ReadBalances",
|
||||
"ReadTransactionsDetail"
|
||||
],
|
||||
"expirationDateTime": "2026-11-06T13:21:06.557675Z"
|
||||
},
|
||||
"links": {
|
||||
"self": "/account-consents/consent-46965b4ef205"
|
||||
},
|
||||
"meta": {}
|
||||
}
|
||||
```
|
||||
---
|
||||
DELETE
|
||||
/account-consents/{consent_id}
|
||||
```bash
|
||||
curl -X 'DELETE' \
|
||||
'https://vbank.open.bankingapi.ru/account-consents/consent-46965b4ef205' \
|
||||
-H 'accept: */*' \
|
||||
-H 'x-fapi-interaction-id: team275-6'
|
||||
```
|
||||
|
||||
204
|
||||
access-control-allow-credentials: true access-control-allow-origin: https://vbank.open.bankingapi.ru content-security-policy: upgrade-insecure-requests date: Thu,06 Nov 2025 13:30:50 GMT server: nginx/1.29.2 strict-transport-security: max-age=63072000; includeSubDomains; preload vary: Origin
|
||||
|
||||
---
|
||||
|
||||
GET
|
||||
/accounts
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://vbank.open.bankingapi.ru/accounts?client_id=team275-6' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-consent-id: consent-46965b4ef205' \
|
||||
-H 'x-requesting-bank: team275'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"account": [
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"status": "Enabled",
|
||||
"currency": "RUB",
|
||||
"accountType": "Personal",
|
||||
"accountSubType": "Checking",
|
||||
"nickname": "Checking счет",
|
||||
"openingDate": "2024-10-30",
|
||||
"account": [
|
||||
{
|
||||
"schemeName": "RU.CBR.PAN",
|
||||
"identification": "4081781027506084636",
|
||||
"name": "Пенсионеров Пенсионер Пенсионерович (team275)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": {
|
||||
"self": "/accounts"
|
||||
},
|
||||
"meta": {
|
||||
"totalPages": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://vbank.open.bankingapi.ru/accounts/acc-3846' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-consent-id: consent-46965b4ef205' \
|
||||
-H 'x-requesting-bank: team275'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"account": [
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"status": "Enabled",
|
||||
"currency": "RUB",
|
||||
"accountType": "Personal",
|
||||
"accountSubType": "Checking",
|
||||
"description": "checking account",
|
||||
"nickname": "Checking счет",
|
||||
"openingDate": "2024-10-30"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://vbank.open.bankingapi.ru/accounts/acc-3846/balances' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-consent-id: consent-46965b4ef205' \
|
||||
-H 'x-requesting-bank: team275'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"balance": [
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"type": "InterimAvailable",
|
||||
"dateTime": "2025-11-06T14:50:59.151865Z",
|
||||
"amount": {
|
||||
"amount": "121398.37",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Credit"
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"type": "InterimBooked",
|
||||
"dateTime": "2025-11-06T14:50:59.151876Z",
|
||||
"amount": {
|
||||
"amount": "121398.37",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Credit"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://vbank.open.bankingapi.ru/accounts/acc-3846/transactions?from_booking_date_time=2025-01-01T00%3A00%3A00Z&to_booking_date_time=2025-12-31T23%3A59%3A59Z&page=1&limit=3' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-consent-id: consent-46965b4ef205' \
|
||||
-H 'x-requesting-bank: team275'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"transaction": [
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m0-1",
|
||||
"amount": {
|
||||
"amount": "114911.38",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Credit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-10-28T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-10-28T17:59:45.080562Z",
|
||||
"transactionInformation": "💼 Зарплата",
|
||||
"bankTransactionCode": {
|
||||
"code": "ReceivedCreditTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m0-5",
|
||||
"amount": {
|
||||
"amount": "8378.49",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Debit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-10-25T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-10-25T17:59:45.080562Z",
|
||||
"transactionInformation": "🎬 Развлечения/Покупки",
|
||||
"bankTransactionCode": {
|
||||
"code": "IssuedDebitTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m0-4",
|
||||
"amount": {
|
||||
"amount": "2707.49",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Debit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-10-22T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-10-22T17:59:45.080562Z",
|
||||
"transactionInformation": "🚌 Транспорт",
|
||||
"bankTransactionCode": {
|
||||
"code": "IssuedDebitTransfer"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": {
|
||||
"self": "/accounts/acc-3846/transactions?page=1&limit=3",
|
||||
"next": "/accounts/acc-3846/transactions?page=2&limit=3"
|
||||
},
|
||||
"meta": {
|
||||
"totalPages": 24,
|
||||
"totalRecords": 70,
|
||||
"currentPage": 1,
|
||||
"pageSize": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
следующие 6 страниц:
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'https://vbank.open.bankingapi.ru/accounts/acc-3846/transactions?from_booking_date_time=2025-01-01T00%3A00%3A00Z&to_booking_date_time=2025-12-31T23%3A59%3A59Z&page=2&limit=6' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'x-consent-id: consent-46965b4ef205' \
|
||||
-H 'x-requesting-bank: team275'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"transaction": [
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m1-1",
|
||||
"amount": {
|
||||
"amount": "135275.00",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Credit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-09-28T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-09-28T17:59:45.080562Z",
|
||||
"transactionInformation": "💼 Зарплата",
|
||||
"bankTransactionCode": {
|
||||
"code": "ReceivedCreditTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m1-5",
|
||||
"amount": {
|
||||
"amount": "7164.52",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Debit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-09-25T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-09-25T17:59:45.080562Z",
|
||||
"transactionInformation": "🎬 Развлечения/Покупки",
|
||||
"bankTransactionCode": {
|
||||
"code": "IssuedDebitTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m1-4",
|
||||
"amount": {
|
||||
"amount": "3052.32",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Debit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-09-22T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-09-22T17:59:45.080562Z",
|
||||
"transactionInformation": "🚌 Транспорт",
|
||||
"bankTransactionCode": {
|
||||
"code": "IssuedDebitTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m1-3",
|
||||
"amount": {
|
||||
"amount": "20870.88",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Debit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-09-20T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-09-20T17:59:45.080562Z",
|
||||
"transactionInformation": "🏠 ЖКХ/Аренда",
|
||||
"bankTransactionCode": {
|
||||
"code": "IssuedDebitTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m1-6",
|
||||
"amount": {
|
||||
"amount": "22441.23",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Credit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-09-18T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-09-18T17:59:45.080562Z",
|
||||
"transactionInformation": "💰 Подработка/Бонус",
|
||||
"bankTransactionCode": {
|
||||
"code": "ReceivedCreditTransfer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "acc-3846",
|
||||
"transactionId": "tx-team275-6-m1-2",
|
||||
"amount": {
|
||||
"amount": "7699.96",
|
||||
"currency": "RUB"
|
||||
},
|
||||
"creditDebitIndicator": "Debit",
|
||||
"status": "Booked",
|
||||
"bookingDateTime": "2025-09-15T17:59:45.080562Z",
|
||||
"valueDateTime": "2025-09-15T17:59:45.080562Z",
|
||||
"transactionInformation": "🏪 Продукты",
|
||||
"bankTransactionCode": {
|
||||
"code": "IssuedDebitTransfer"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": {
|
||||
"self": "/accounts/acc-3846/transactions?page=2&limit=6",
|
||||
"next": "/accounts/acc-3846/transactions?page=3&limit=6",
|
||||
"prev": "/accounts/acc-3846/transactions?page=1&limit=6"
|
||||
},
|
||||
"meta": {
|
||||
"totalPages": 12,
|
||||
"totalRecords": 70,
|
||||
"currentPage": 2,
|
||||
"pageSize": 6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://wasm-bindgen.github.io/wasm-bindgen/?search=logging
|
||||
46
20-dev/00-rust/wasm rust (frontend dioxus leptos)/logger.md
Normal file
46
20-dev/00-rust/wasm rust (frontend dioxus leptos)/logger.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
```rust
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_logger::tracing::{Level, info};
|
||||
|
||||
fn main() {
|
||||
dioxus_logger::init(Level::INFO).expect("logger failed to init");
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
info!("App rendered");
|
||||
rsx! {
|
||||
p { "hi" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::logger::tracing::{Level, info};
|
||||
|
||||
fn main() {
|
||||
dioxus::logger::init(Level::INFO).expect("logger failed to init");
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
info!("App rendered");
|
||||
rsx! {
|
||||
p { "hi" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// Включает стандартный форматер с временем в UTC
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
tracing::info!("Это сообщение будет с временной меткой.");
|
||||
}
|
||||
```
|
||||
|
||||
https://crates.io/crates/dioxus-logger
|
||||
0
20-dev/10-go/wait group.md
Normal file
0
20-dev/10-go/wait group.md
Normal file
130
20-dev/20-database/Untitled.md
Normal file
130
20-dev/20-database/Untitled.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
You are absolutely right. **My previous example was "Tutorial Mode" (easy to start), not "Professional/Solid Mode".**
|
||||
|
||||
For a "Solid Practice" workflow, **you should never commit credentials** to `reshape.toml`. Since you are already using **SOPS**, we should leverage it to inject the credentials at runtime.
|
||||
|
||||
Here is the **Professional "Rustacean" Workflow** using `sops`, `just`, and environment variables.
|
||||
|
||||
### 1. The Strategy
|
||||
|
||||
1. **`.env.enc` (Encrypted)**: Holds the `POSTGRES_PASSWORD` and `POSTGRES_USER`.
|
||||
2. **`reshape.toml` (Generic)**: Contains only structure (host, port, db name), **no secrets**.
|
||||
3. **`Justfile` (The Glue)**: Unwraps the secrets from SOPS and injects them as environment variables (`PGPASSWORD`) which the underlying Rust Postgres driver (used by Reshape) should respect.
|
||||
|
||||
***
|
||||
|
||||
### 2. Implementation
|
||||
|
||||
#### Step A: Configure `.env` and Encrypt it
|
||||
Create your `.env` (or edit your existing one) to include the standard Postgres environment variables.
|
||||
|
||||
```bash
|
||||
# .env (Local unencrypted version for setup)
|
||||
POSTGRES_USER=bootcamp
|
||||
POSTGRES_PASSWORD=my_super_secure_password
|
||||
POSTGRES_DB=pizzeria
|
||||
# Standard env var that libpq/rust-postgres clients read automatically
|
||||
PGPASSWORD=my_super_secure_password
|
||||
PGUSER=bootcamp
|
||||
PGHOST=localhost
|
||||
```
|
||||
|
||||
**Encrypt it with SOPS:**
|
||||
*(Assuming you ran `just setup-secrets` from my previous answer)*
|
||||
```bash
|
||||
sops --encrypt --age $(grep -oP "public key: \K.*" .config/keys.txt) --input-type
|
||||
dotenv --output-type dotenv .env > .env.enc
|
||||
```
|
||||
*Add `.env` to `.gitignore`. Commit `.env.enc`.*
|
||||
|
||||
#### Step B: Clean `reshape.toml`
|
||||
|
||||
Remove the credentials from the configuration file.
|
||||
|
||||
```toml
|
||||
# reshape.toml
|
||||
[database]
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
name = "pizzeria"
|
||||
# username = ... <-- REMOVE THESE
|
||||
# password = ... <-- REMOVE THESE
|
||||
|
||||
[migrations]
|
||||
directory = "migrations"
|
||||
```
|
||||
|
||||
#### Step C: Update `Justfile` (The Magic)
|
||||
|
||||
We will use `sops exec-env` to inject the `PGPASSWORD` variable into the environment before `reshape` starts.
|
||||
|
||||
**How this works:**
|
||||
Most Rust database tools (including those using `tokio-postgres` which Reshape likely uses) will look for standard environment variables like `PGPASSWORD` or `PGUSER` if they are missing from the explicit config.
|
||||
|
||||
```just
|
||||
set dotenv-load
|
||||
|
||||
# Wrapper to run commands with decrypted secrets
|
||||
with-secrets cmd:
|
||||
sops exec-env .env.enc "{{cmd}}"
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# DATABASE MIGRATION COMMANDS (Using Sops)
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# Init: Starts DB and runs migrations securely
|
||||
init: up
|
||||
@echo "Waiting for Postgres..."
|
||||
@sleep 2
|
||||
just migrate
|
||||
|
||||
# Migrate: Injects PGPASSWORD/PGUSER from .env.enc -> reshape
|
||||
migrate:
|
||||
@echo "Applying migrations..."
|
||||
# We explicitly export PGUSER/PGPASSWORD from the Sops env for Reshape
|
||||
sops exec-env .env.enc -- reshape migrate
|
||||
|
||||
# Create new migration
|
||||
add name:
|
||||
reshape migration create {{name}}
|
||||
|
||||
# Undo/Abort
|
||||
undo:
|
||||
sops exec-env .env.enc -- reshape migration abort
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# INFRASTRUCTURE
|
||||
# ---------------------------------------------------------
|
||||
|
||||
up:
|
||||
# Docker also needs the secrets to start the container
|
||||
just with-secrets "docker-compose up -d"
|
||||
|
||||
down:
|
||||
docker-compose down
|
||||
|
||||
tui:
|
||||
# Gobang also needs secrets
|
||||
just with-secrets "gobang -h localhost -p 5432 -d ${POSTGRES_DB}"
|
||||
```
|
||||
|
||||
### 3. The "Paranoid" Alternative (Template Rendering)
|
||||
|
||||
If `reshape` (in some specific version) decides to **ignore** environment variables and *strictly* demands a password in `reshape.toml`, the "Solid" pattern is **Config Generation**.
|
||||
|
||||
You would treat `reshape.toml` as a template.
|
||||
|
||||
1. Create `reshape.toml.tmpl` (with placeholders like `${POSTGRES_PASSWORD}`).
|
||||
2. Add `reshape.toml` to `.gitignore`.
|
||||
3. Update `Justfile` to render it on the fly:
|
||||
|
||||
```just
|
||||
# Justfile (Alternative "Template" approach)
|
||||
migrate:
|
||||
# 1. Decrypt env
|
||||
# 2. Use envsubst to replace vars in template -> create real toml
|
||||
# 3. Run reshape
|
||||
# 4. Delete real toml (cleanup)
|
||||
sops exec-env .env.enc -- bash -c "envsubst < reshape.toml.tmpl > reshape.toml && reshape migrate && rm reshape.toml"
|
||||
```
|
||||
|
||||
**My Recommendation:** Try the **Environment Variable (Step C)** method first. It is cleaner, standard for "Cloud Native" apps, and doesn't involve creating/deleting temporary files. Only use the Template method if the tool forces you to.
|
||||
0
20-dev/README.md
Normal file
0
20-dev/README.md
Normal file
0
20-dev/c/README.md
Normal file
0
20-dev/c/README.md
Normal file
0
20-dev/cpp/README.md
Normal file
0
20-dev/cpp/README.md
Normal file
0
20-dev/python/README.md
Normal file
0
20-dev/python/README.md
Normal file
Loading…
Add table
Add a link
Reference in a new issue