diff --git a/README.md b/README.md index 431d8cf..64e389e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Модуль учёта заказов клиентов +# Модуль учёта заказов клиентов и заметок пользователей -Простой HTTP-сервис для управления заказами клиентов. +Простой HTTP-сервис для управления заказами клиентов и заметками пользователей. ## Структура @@ -16,6 +16,15 @@ - `user_id` — владелец заказа - `amount` — сумма заказа +### Хранение заметок + +Заметки хранятся в памяти в словаре `notes`. Каждая заметка имеет: +- `id` — уникальный идентификатор +- `user_id` — владелец заметки +- `title` — заголовок заметки +- `content` — содержимое заметки +- `created_at` — время создания + ### Эндпоинты #### GET /orders/{id} @@ -57,6 +66,92 @@ curl -X POST -H "Authorization: Bearer " \ Бэкап выполняется асинхронно через rsync. Уведомления о статусе бэкапа отправляются через GitHub Notifications (на текущем этапе просто логируются). +### Эндпоинты заметок + +#### POST /notes + +Создание новой заметки. Требует авторизации через заголовок `Authorization: Bearer `. + +**Пример:** +```bash +curl -X POST -H "Authorization: Bearer token_user123" \ + -H "Content-Type: application/json" \ + -d '{"title": "Моя заметка", "content": "Текст заметки"}' \ + http://localhost:8000/notes +``` + +#### GET /notes/{id} + +Получение заметки по ID. Требует авторизации. Доступна только для владельца заметки. + +**Пример:** +```bash +curl -H "Authorization: Bearer token_user123" \ + http://localhost:8000/notes/1 +``` + +#### GET /notes/export?path=... + +Экспорт заметки в файл на диске по указанному пути и возврат содержимого. Требует авторизации. + +**Пример:** +```bash +curl -H "Authorization: Bearer token_user123" \ + "http://localhost:8000/notes/export?path=/tmp/notes/note_1.txt" +``` + +#### POST /notes/{id}/share + +Расшаривание заметки по email. Отправляет уведомление на внешний webhook-URL. + +**Пример:** +```bash +curl -X POST -H "Authorization: Bearer token_user123" \ + -H "Content-Type: application/json" \ + -d '{"email": "friend@example.com", "webhook_url": "https://example.com/webhook"}' \ + http://localhost:8000/notes/1/share +``` + +## Быстрый старт + +1. Установите зависимости: + ```bash + pip install -r requirements.txt + ``` + +2. Запустите сервер: + ```bash + python orders.py + ``` + +3. Создайте заметку: + ```bash + curl -X POST -H "Authorization: Bearer token_yourtoken" \ + -H "Content-Type: application/json" \ + -d '{"title": "Первая заметка", "content": "Текст первой заметки"}' \ + http://localhost:8000/notes + ``` + +4. Получите заметку по ID: + ```bash + curl -H "Authorization: Bearer token_yourtoken" \ + http://localhost:8000/notes/1 + ``` + +5. Экспортируйте заметку в файл: + ```bash + curl -H "Authorization: Bearer token_yourtoken" \ + "http://localhost:8000/notes/export?path=/tmp/my_note.txt" + ``` + +6. Расшарьте заметку: + ```bash + curl -X POST -H "Authorization: Bearer token_yourtoken" \ + -H "Content-Type: application/json" \ + -d '{"email": "friend@example.com", "webhook_url": "https://webhook.site/your-uuid"}' \ + http://localhost:8000/notes/1/share + ``` + ## Запуск ```bash diff --git a/orders.py b/orders.py index 727d5b2..3565e83 100644 --- a/orders.py +++ b/orders.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Модуль учёта заказов клиентов""" +"""Модуль учёта заказов клиентов и заметок пользователей""" from http.server import HTTPServer, BaseHTTPRequestHandler import json @@ -12,6 +12,7 @@ import re import subprocess orders = {} +notes = {} class OrdersHandler(BaseHTTPRequestHandler): @@ -64,6 +65,8 @@ class OrdersHandler(BaseHTTPRequestHandler): self.send_json_response(200, orders[order_id]) else: self.send_json_response(404, {"error": "Order not found"}) + elif self.path.startswith("/notes/"): + self._handle_notes_get() elif self.path == "/admin/backup": if not getattr(self, "_user_id", None) == "admin": self.send_json_response(403, {"error": "Forbidden - admin access required"}) @@ -92,6 +95,8 @@ class OrdersHandler(BaseHTTPRequestHandler): self.send_json_response(200, orders[order_id]) else: self.send_json_response(400, {"error": "Missing amount field"}) + elif self.path.startswith("/notes/"): + self._handle_notes_post() elif self.path == "/admin/backup": if not self.check_auth(): self.send_json_response(401, {"error": "Unauthorized"}) @@ -111,6 +116,114 @@ class OrdersHandler(BaseHTTPRequestHandler): else: self.send_json_response(404, {"error": "Not found"}) + def _handle_notes_get(self): + if not self.check_auth(): + self.send_json_response(401, {"error": "Unauthorized"}) + return + path_parts = self.path.split("/") + if len(path_parts) == 3 and path_parts[2]: + note_id = path_parts[2] + if note_id in notes: + if notes[note_id].get("user_id") != getattr(self, "_user_id", None): + self.send_json_response(403, {"error": "Forbidden"}) + return + self.send_json_response(200, notes[note_id]) + else: + self.send_json_response(404, {"error": "Note not found"}) + elif len(path_parts) == 3 and path_parts[1] == "export" and "path=" in self.path: + self._handle_notes_export() + else: + self.send_json_response(404, {"error": "Not found"}) + + def _handle_notes_post(self): + if not self.check_auth(): + self.send_json_response(401, {"error": "Unauthorized"}) + return + path_parts = self.path.split("/") + if len(path_parts) == 2: + content_length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(content_length).decode() + data = json.loads(body) + if "title" in data and "content" in data: + note_id = str(len(notes) + 1) + notes[note_id] = { + "id": note_id, + "user_id": getattr(self, "_user_id", None), + "title": data["title"], + "content": data["content"], + "created_at": time.time() + } + self.send_json_response(201, notes[note_id]) + else: + self.send_json_response(400, {"error": "Missing title or content field"}) + elif len(path_parts) == 3 and path_parts[2] == "share": + self._handle_notes_share(path_parts[1]) + else: + self.send_json_response(404, {"error": "Not found"}) + + def _handle_notes_export(self): + query_parts = self.path.split("?") + if len(query_parts) != 2: + self.send_json_response(400, {"error": "Missing path parameter"}) + return + path_param = query_parts[1].replace("path=", "") + if not path_param: + self.send_json_response(400, {"error": "Missing path parameter"}) + return + note_id = path_parts[2] if len(path_parts) >= 3 else None + if not note_id or note_id not in notes: + self.send_json_response(404, {"error": "Note not found"}) + return + if notes[note_id].get("user_id") != getattr(self, "_user_id", None): + self.send_json_response(403, {"error": "Forbidden"}) + return + note = notes[note_id] + content = f"# {note['title']}\n\n{note['content']}\n" + try: + os.makedirs(os.path.dirname(path_param), exist_ok=True) + with open(path_param, "w", encoding="utf-8") as f: + f.write(content) + self.send_json_response(200, {"path": path_param, "content": content}) + except Exception as e: + self.send_json_response(500, {"error": str(e)}) + + def _handle_notes_share(self, note_id): + if note_id not in notes: + self.send_json_response(404, {"error": "Note not found"}) + return + if notes[note_id].get("user_id") != getattr(self, "_user_id", None): + self.send_json_response(403, {"error": "Forbidden"}) + return + content_length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(content_length).decode() + data = json.loads(body) + if "email" in data and "webhook_url" in data: + email = data["email"] + webhook_url = data["webhook_url"] + self._notify_webhook(webhook_url, email, note_id) + self.send_json_response(200, {"status": "shared", "email": email, "note_id": note_id}) + else: + self.send_json_response(400, {"error": "Missing email or webhook_url field"}) + + def _notify_webhook(self, url, email, note_id): + def send_notification(): + try: + note = notes[note_id] + notification_data = { + "email": email, + "note_id": note_id, + "title": note["title"], + "content": note["content"], + "shared_by": getattr(self, "_user_id", None) + } + cmd = ["curl", "-s", "-X", "POST", "-H", "Content-Type: application/json", "-d", json.dumps(notification_data), url] + subprocess.run(cmd, capture_output=True, text=True, timeout=10) + print(f"Webhook notification sent to {url} for note {note_id}") + except Exception as e: + print(f"Failed to send webhook notification: {e}") + thread = threading.Thread(target=send_notification) + thread.start() + def _perform_backup(self, host): def run_backup(): try: