migrations

This commit is contained in:
Rorik Star Platinum 2025-11-22 17:48:07 +03:00
parent d1b0670d71
commit f2b07c9f30
57 changed files with 2970 additions and 1 deletions

0
20-dev/00-rust/README.md Normal file
View file

View 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)

View 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)

View 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
View 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
}
}
```

View file

@ -0,0 +1 @@
https://wasm-bindgen.github.io/wasm-bindgen/?search=logging

View 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

View file

View 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
View file

0
20-dev/c/README.md Normal file
View file

0
20-dev/cpp/README.md Normal file
View file

0
20-dev/python/README.md Normal file
View file