Skip to content
Open
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
35 changes: 35 additions & 0 deletions simple_backend/src/task_tracker/base_http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from abc import ABC, abstractmethod
import requests
import json

class BaseHTTPClient(ABC):
def __init__(self):
self.base_url = self.get_base_url()
self.headers = self.get_headers()

@abstractmethod
def get_base_url(self) -> str:

pass

@abstractmethod
def get_headers(self) -> dict:

pass

def make_request(self, method: str, endpoint: str = "", json_data: dict = None, data: str = None, timeout: int = 30):

url = f"{self.base_url}{endpoint}"

try:
response = requests.request(
method=method,
url=url,
headers=self.headers,
json=json_data,
data=data,
timeout=timeout
)
return response
except Exception as e:
raise Exception(f"Request error: {str(e)}")
50 changes: 50 additions & 0 deletions simple_backend/src/task_tracker/cloudflare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
from base_http_client import BaseHTTPClient

class CloudflareAI(BaseHTTPClient):
def __init__(self):
self.account_id = os.getenv('CLOUDFLARE_ACCOUNT_ID')
self.api_token = os.getenv('CLOUDFLARE_API_TOKEN')
self.model = '@cf/meta/llama-3-8b-instruct'
super().__init__()

def get_base_url(self) -> str:
return f"https://api.cloudflare.com/client/v4/accounts/{self.account_id}/ai/run/{self.model}"

def get_headers(self) -> dict:
return {
"Authorization": f"Bearer {self.api_token}",
"Content-Type": "application/json"
}

def get_advice(self, task_text: str) -> str:
"""Получает советы по решению задачи от LLM"""
if not self.account_id or not self.api_token:
return "AI не настроен"

try:
payload = {
"messages": [
{
"role": "system",
"content": "Ты - полезный ассистент. Отвечай ТОЛЬКО на русском языке. Давай четкие, структурированные ответы с нумерованными шагами."
},
{
"role": "user",
"content": f"Дай подробные шаги для решения этой задачи на русском языке: {task_text}"
}
]
}

response = self.make_request("POST", json_data=payload, timeout=30)

print(f"Cloudflare Status: {response.status_code}")

if response.status_code == 200:
result = response.json()
return result.get('result', {}).get('response', 'Ответ получен')
else:
return f"Ошибка API: {response.status_code} - {response.text}"

except Exception as e:
return f"Ошибка: {str(e)}"
56 changes: 56 additions & 0 deletions simple_backend/src/task_tracker/gist_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json
import os
from base_http_client import BaseHTTPClient

class GistStorage(BaseHTTPClient):
def __init__(self, filename='tasks.json'):
self.json_name = filename
self.gist_id = os.getenv('GIST_ID')
self.github_token = os.getenv('GITHUB_TOKEN')
super().__init__()

def get_base_url(self) -> str:
return "https://api.github.com"

def get_headers(self) -> dict:
return {
"Authorization": f"token {self.github_token}",
"Accept": "application/vnd.github.v3+json"
}

def load_tasks(self):
if not self.gist_id or not self.github_token:
return []

try:
response = self.make_request("GET", f"/gists/{self.gist_id}")

if response.status_code == 200:
gist_data = response.json()
tasks_content = gist_data['files'][self.json_name]['content']
return json.loads(tasks_content)
return []
except:
return []

def save_tasks(self, task_list):
if not self.gist_id or not self.github_token:
return False

try:
gist_data = {
"files": {
self.json_name: {
"content": json.dumps(task_list, indent=2, ensure_ascii=False)
}
}
}

response = self.make_request(
"PATCH",
f"/gists/{self.gist_id}",
data=json.dumps(gist_data)
)
return response.status_code == 200
except:
return False
67 changes: 56 additions & 11 deletions simple_backend/src/task_tracker/main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from gist_storage import GistStorage
from cloudflare import CloudflareAI
from dotenv import load_dotenv

load_dotenv()

app = FastAPI()

@app.get("/tasks")
storage = GistStorage()
ai_client = CloudflareAI()

class CreateTask(BaseModel):
task: str
status: str = 'No'

class UpdateTask(BaseModel):
task: str
status: str

@app.get("/tasks", tags=['Вывод всех задач'])
def get_tasks():
pass
return storage.load_tasks()

@app.post("/tasks")
def create_task(task):
pass
@app.post("/tasks", tags=['Добавление задачи'])
def create_task(new_task: CreateTask):
tasks = storage.load_tasks()
max_id = max(task['id'] for task in tasks) + 1 if tasks else 1

ai_advice = ai_client.get_advice(new_task.task)
enhanced_task = f"{new_task.task}\n\nСоветы по решению:\n{ai_advice}"

tasks.append({
'id': max_id,
'task': enhanced_task,
'status': new_task.status
})

storage.save_tasks(tasks)
return {"success": True}

@app.put("/tasks/{task_id}")
def update_task(task_id: int):
pass
@app.put("/tasks/{task_id}", tags=['Обновление задачи'])
def update_task(task_id: int, update_task: UpdateTask):
tasks = storage.load_tasks()
for i in tasks:
if i['id'] == task_id:
i['task'] = update_task.task
i['status'] = update_task.status
storage.save_tasks(tasks)
return {"success": True}

raise HTTPException(status_code=404, detail='Задача не найдена')

@app.delete("/tasks/{task_id}")
@app.delete("/tasks/{task_id}", tags=['Удаление задачи'])
def delete_task(task_id: int):
pass
tasks = storage.load_tasks()
for i, task in enumerate(tasks):
if task['id'] == task_id:
tasks.pop(i)
storage.save_tasks(tasks)
return {"success": True}

raise HTTPException(status_code=404, detail='Задача не найдена')
63 changes: 63 additions & 0 deletions simple_backend/src/task_tracker/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
**В чём минусы подхода с хранением задач в оперативной памяти (списке python)?**

Минусы хранения задач в оперативной памяти (списке Python):
1 - Потеря данных при перезапуске
2 - Ограниченный объем памяти
3 - Данные существуют только во время работы программы

## после перехода на json файл
**Что улучшилось после того, как список из оперативной памяти изменился на файл проекта?**

Улучшения:
1. Сохраняемость данных - задачи не теряются при перезапуске сервера
2. Устойчивость к сбоям - данные сохраняются на диск, а не только в RAM
3. Прозрачность данных - можно открыть файл и посмотреть задачи вручную

Остались проблемы:
1. Производительность - чтение/запись файла медленнее чем работа с памятью
2. Состояние гонки - при одновременных запросах возможна потеря данных

**Избавились ли мы от хранения состояния?**
Нет, Backend остался STATEful - он по-прежнему хранит данные между запросами, просто теперь они сохраняются на диск.

**Где еще можно хранить задачи и какие есть преимущества и недостатки этих подходов?**
### 1. Реляционные БД (PostgreSQL, MySQL)
Плюсы:
- ACID-транзакции
- Сложные запросы (JOIN, GROUP BY)
- Целостность данных
- Одновременный доступ

Минусы:
- Сложность настройки
- Оверкилл для простых проектов
- Требует отдельного сервера БД

### 2. Облачные хранилища
Плюсы:
- Доступность из любого места
- Автоматическое резервное копирование
- Не нужно настраивать сервер

Минусы:
- Зависимость от внешнего сервиса
- Лимиты запросов
- Проблемы с доступностью

**Прочитайте что такое "состояние гонки" и напишите в readme файле о том, какие проблемы остались в бекенде на данном этапе проекта. Есть ли у вас какое-то решение этой проблемы?**

При одновременных запросах возможна потеря данных:
- Два пользователя добавляют задачи → одна задача теряется
- Редактирование и удаление одновременно → повреждение данных
- Изменения статусов конфликтуют

## Решение проблемы

mb Реализовать оптимистичную блокировку с повторными попытками:

def update_with_retry(task_id, update_data, max_attempts=3):
for attempt in range(max_attempts):
tasks = load_tasks()
if save_tasks(tasks):
return True
return False #
20 changes: 20 additions & 0 deletions simple_backend/src/task_tracker/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
import os

class FileStorage:
def __init__(self, filename='tasks.json'):
self.json_name = filename

def load_tasks(self):
if not os.path.exists(self.json_name):
return []

try:
with open(self.json_name, 'r', encoding='utf-8') as file:
return json.load(file)
except (FileNotFoundError, json.JSONDecodeError):
return []

def save_tasks(self, task_list):
with open(self.json_name, 'w', encoding='utf-8') as file:
json.dump(task_list, file)
14 changes: 14 additions & 0 deletions simple_backend/src/task_tracker/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"id": 1,
"task": "Закончить простые бэки",
"status": "No"
},

{
"id": 2,
"task": "Хз",
"status": "Yes"
}

]