Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #40

Merged
merged 7 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 329 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
<p align="center">
<a href="https://ibb.co/KyLXkwW"><img src="https://i.ibb.co/9YVNLtW/full-logo.png" alt="full-logo" border="0"></a>
</p>

<p align="center">
<img src="https://img.shields.io/badge/python-3.10-green" alt="Python Version">
<img src="https://img.shields.io/badge/fastapi-red" alt="Python Version">
<img src="https://img.shields.io/badge/sqlalchemy-orange" alt="Python Version">
<img src="https://img.shields.io/badge/docker-blue" alt="Python Version">
</p>

## Про проект

Адаптивний сервіс для класифікації та моніторингу персональних витрат.
Має CRUD операції для керування операціями та категоріями.
Створює персональну статистику витрат, яку можна візуалізувати у діаграмах.
Дозволяє створювати обмеження на витрати по категоріям.
Має можливість синхронізуватись з банками (Monobank) для отримання витрат у реальному часі.

## Про розробку

Проектування проекту відбувалось по принципу DDD та чистої архітектури.
Він поділений на шари бізнес-логіки, доменів, адаптерів та інфраструктури.
За допомогою Dependency Inversion вдалось побудувати правильну ієрархію залежностей:
```
Domens <- Uses cases <- adapters <- infrastructure
```
Де стрілками показана залежність шару від іншого.
Тобто, інфраструктура залежить від вищих шарів, адаптери від бізнес логіки та доменів,
бізнес логіка тільки від доменів а домени це незалежні частини програмного продукту.

Розроблявся додаток за принципом TDD, де спочатчку для правильного проектування структур і алгоритмів
розроблялись тести, де формувались вимоги до коду, а потім розроблявся сам код щоб ці вимоги задовольнити.

Для автоматичних тестів та розгортання був застосований GitHub Actions. Для контейнеризації був застосований Docker.


## Документація

### `GET` `/users/` - get current user info

**Headers**
```
Authorization: <token_type> <access_token>
```

**Status codes:**

| Status Code | Description |
|:-------------:|:-------------:|
| `200` | Ok |
| `401` | Unauthorized |

**Response:**
```
{
"id": int,
"email": string,
}
```
---
### `POST` `/users/` - create user

**Request data:**
```
{
"email": string,
"password": string,
}
```

**Status codes:**

| Status Code | Description |
|:-----------:|:-----------------:|
| `201` | Created |
| `422` | Validation Error |

**Response data:**
```
{
"id": int,
"email": string,
}
```
---
### `POST` `/token/` - login for access token

**Request data:**
```
{
"username": string (email),
"password": string,
}
```

**Status codes:**

| Status Code | Description |
|:-----------:|:----------------:|
| `200` | Ok |
| `422` | Validation Error |

**Response data:**
```
{
"access_token": "string",
"token_type": "string"
}
```
---
### `POST` `/operations/` - create operation

**Headers**
```
Authorization: <token_type> <access_token>
```

**Request data:**
```
{
"amount": int,
"description": "string",
"source_type": "string",
"time": int,
"category_id": int
}
```

**Status codes:**

| Status Code | Description |
|:-----------:|:-----------------:|
| `201` | Created |
| `422` | Validation Error |

**Response data:**
```
{
"id": int,
"amount": int,
"description": "string",
"source_type": "string",
"time": int,
"category_id": int,
"subcategory_id": int
}
```
---
### `GET` `/operations/` - get list of operations

**Headers**
```
Authorization: <token_type> <access_token>
```

**Request parameters:**
```
from_time: int
to_time: int
```

**Status codes:**

| Status Code | Description |
|:-----------:|:-----------------:|
| `200` | Created |
| `422` | Validation Error |

**Response data:**
```
[
{
"amount": int,
"description": "string",
"source_type": "string",
"time": int,
"category_id": int,
"id": int,
"subcategory_id": int
}
]
```
---
### `GET` `/bankapi/` - get list of connected banks names

**Headers**
```
Authorization: <token_type> <access_token>
```
**Status codes:**

| Status Code | Description |
|:-----------:|:-----------:|
| `200` | Ok |


**Response data:**
```
[
"string"
]
```
---
### `DELETE` `/bankapi/` - delete record of connect to bank

**Headers**
```
Authorization: <token_type> <access_token>
```

**Request parameters:**
```
bank_name: string
```

**Status codes:**

| Status Code | Description |
|:-----------:|:----------------:|
| `204` | No content |
| `422` | Validation Error |

---
### `GET` `/bankapi/costs/` - update costs by banks API

**Headers**
```
Authorization: <token_type> <access_token>
```

**Status codes:**

| Status Code | Description |
|:-----------:|:-----------:|
| `200` | Ok |
---
### `GET` `/statistic/` - get statistic

**Headers**
```
Authorization: <token_type> <access_token>
```

**Request parameters:**
```
from_time: int
to_time: int
```

**Status codes:**

| Status Code | Description |
|:-----------:|:-----------------:|
| `200` | Created |

**Response data:**
```
{
"costs_sum": 0,
"categories_costs": {},
"costs_num_by_days": {},
"costs_sum_by_days": {}
}
```
---
### `POST` `/categories/` - create category

**Headers**
```
Authorization: <token_type> <access_token>
```

**Request data:**
```
{
"name": "string",
"icon_name": "string",
"icon_color": "string"
}
```

**Status codes:**

| Status Code | Description |
|:-----------:|:-----------------:|
| `201` | Created |
| `422` | Validation Error |

**Response data:**
```
{
"name": "string",
"id": int,
"user_id": int,
"type": "string",
"icon_name": "string",
"icon_color": "string",
"parent_id": int
}
```
---
### `GET` `/categories/` - get list of categories

**Headers**
```
Authorization: <token_type> <access_token>
```

**Status codes:**

| Status Code | Description |
|:-----------:|:----------------:|
| `200` | Ok |

**Response data:**
```
[
{
"name": "string",
"id": int,
"user_id": int,
"type": "string",
"icon_name": "string",
"icon_color": "string",
"parent_id": int
}
]
```
13 changes: 4 additions & 9 deletions src/app/adapters/orm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Технічні особливості побудови таблиць в ORM
Technical aspects of table construction in ORM and mapping to domain models
"""

from sqlalchemy import Column, ForeignKey, Integer, String, Table
Expand Down Expand Up @@ -28,11 +28,10 @@ def create_tables(mapper_registry) -> dict[str, Table]:
Column("amount", Integer, nullable=False),
Column("description", String),
Column("time", Integer, nullable=False),
# mcc - код виду операції
Column("source_type", String, nullable=False),
# Тип джерела.
# Source type.
# value: "manual" | "<bank_name>"
# Операція може бути або додана вручну або за допомогою API банку
# The operation can either be added manually or through the bank's API.
Column("user_id", Integer, ForeignKey("users.id")),
Column("category_id", Integer, ForeignKey("categories.id"), nullable=True),
),
Expand Down Expand Up @@ -76,11 +75,7 @@ def create_tables(mapper_registry) -> dict[str, Table]:


def start_mappers(mapper_registry: registry, tables: dict[str, Table]):
"""
Прив'язка класів до таблиць. ORM буде з ними працювати як з моделями
Такий підхід відповідє SOLID, інверсуючи залежності.
Таким чином, частини програми нижчого рівня стають залежними від вищого рівня
"""
"""Binding domain classes to tables. The ORM will work with them as models."""
mapper_registry.map_imperatively(
User,
tables["users"],
Expand Down
15 changes: 7 additions & 8 deletions src/app/repositories/absctract/bank_api.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""
Репозиторії менеджерів банку
Bank Manager Repositories:

ABankInfoRepository:
Відповідає за зберігання моделі BankInfo
яка містить інформацію про банк
Responsible for storing the BankInfo model, which contains information about the bank.

ABankManagerRepository:
Відповідає за роботу з API банка. Для роботи потрібні поля
які находяться в BankInfo моделі
Responsible for working with the bank's API.
It requires fields present in the BankInfo model.

Кожен банк має свій алгоритм для отримання витрат, для якого потрібні різні поля.
Кожен нащадок ABankManagerRepository реалізує роботу з якимось API.
Each bank has its own algorithm for obtaining expenses, which requires different fields.
Each subclass of ABankManagerRepository implements the work with a specific API.
"""

from abc import ABC, abstractmethod
Expand Down Expand Up @@ -61,7 +60,7 @@ async def get_costs(self, from_time=None, to_time=None) -> list[dict]:

class BankManagerRepositoryFactory:
"""
Фабрика для створення ABankInfoRepository на основі BankInfo моделі
Factory for creating ABankInfoRepository based on the BankInfo model.
"""

@staticmethod
Expand Down
Loading