Сучасний REST API для новинного блогу з монетизацією через підписки, закріпленими постами та повною платіжною інтеграцією Stripe.
- Про проект
- Особливості
- Технічний стек
- Архітектура проекту
- Швидкий старт
- API Документація
- Змінні оточення
- Тестування
- Celery задачі
- Що можна додати
News Site API — це повноцінний бекенд для новинного блогу з системою монетизації. Користувачі можуть реєструватися, публікувати пости, коментувати, а також придбати Premium підписку через Stripe, яка дозволяє закріплювати один пост у топі стрічки.
Проект побудований за принципами чистої архітектури: кожен модуль (accounts, main, comments, subscribe, payment) є незалежним Django-додатком зі своїми моделями, серіалізаторами, в'юшками та тестами.
Проект задеплоєний на хмарному сервері з SSL-сертифікатом (Let's Encrypt) та доступний за адресою:
| Сервіс | URL |
|---|---|
| 📘 Swagger UI | newsapi.duckdns.org/api/docs/swagger/ |
| 📗 ReDoc | newsapi.duckdns.org/api/docs/redoc/ |
| ⚙️ Django Admin | newsapi.duckdns.org/admin/ |
| 🔗 API Root | newsapi.duckdns.org/api/v1/ |
Стек деплою: VPS → Docker Compose → Gunicorn → Nginx → Let's Encrypt SSL → DuckDNS
- JWT-Auth (access + refresh токени)
- Реєстрація, вхід, вихід (blacklist токена), оновлення токена
- Зміна паролю з валідацією старого
- Профіль з аватаром, біографією, лічильниками постів та коментарів
- CRUD для постів зі статусами
draft/published - Автогенерація SEO-friendly slug із заголовку
- Лічильник переглядів із автоінкрементом при кожному GET
- Фільтрація за категорією, автором, статусом; пошук по тексту
- Умовна видимість: анонімам — лише опубліковані; авторам — + власні чернетки
- Спеціальні вибірки: популярні (топ-10 за переглядами), нещодавні, рекомендовані
featured
- Дворівнева система: головні коментарі та відповіді (
parent) - Soft delete — замість видалення встановлюється
is_active=False - Дерево коментарів до поста з вкладеними відповідями
- Тарифні плани з гнучкою конфігурацією (
SubscriptionPlan) - Один активний
Subscriptionна користувача (OneToOne) - Автоматичне встановлення дати завершення при активації
- Відміна підписки з автоматичним видаленням закріпленого поста
- Повна
SubscriptionHistory— журнал усіх подій
- Лише підписники можуть закріпити один свій опублікований пост
- Закріплені пости відображаються першими у стрічці
- При закінченні підписки — закріплення знімається автоматично (Celery)
- Валідація на рівні моделі (
PinnedPost.save()) та серіалізатора
- Stripe Checkout Session для безпечної оплати
- Webhook-обробник для синхронізації статусів платежів
- Повернення коштів (refund) через адмін-панель
- Повна історія транзакцій з метаданими
- Аналітика для адміністраторів (дохід, успішність, активні підписки)
- Перевірка прострочених підписок — щогодини
- Email-нагадування про закінчення підписки — щодня
- Очищення старих платежів — щотижня
- Повторна обробка невдалих webhook-подій — щогодини
| Компонент | Технологія |
|---|---|
| Фреймворк | Django 5.2 + Django REST Framework |
| База даних | PostgreSQL 15 |
| Кеш / Брокер | Redis 7 |
| Черги задач | Celery + Celery Beat |
| Автентифікація | JWT (SimpleJWT) з blacklist |
| Платежі | Stripe (Checkout + Webhooks) |
| API Документація | drf-spectacular (Swagger / ReDoc) |
| Контейнеризація | Docker + Docker Compose |
| Веб-сервер | Gunicorn + Nginx |
| SSL | Let's Encrypt |
| pytest + pytest-django + Factory Boy + Faker |
news-site/
├── apps/
│ ├── accounts/ # Реєстрація, авт-ція, профіль, зміна пароля
│ ├── main/ # Пости, категорії, закріплення
│ ├── comments/ # Коментарі (дворівневі, soft delete)
│ ├── subscribe/ # Підписки, плани, закріплені пости
│ │ ├── signals.py # Автоматичне ведення history
│ │ └── tasks.py # Перевірка прострочених підписок
│ └── payment/
│ ├── services.py # StripeService, PaymentService, WebhookService
│ └── tasks.py # Очищення та retry webhook
├── config/
│ ├── settings.py # Налаштування Django
│ ├── celery.py # Конфігурація Celery
│ └── urls.py # Головний роутер
├── tests/ # pytest тести по кожному модулю
├── docker-compose.yml # db, redis, backend, celery, nginx
├── Dockerfile
└── nginx.conf
User ──────────── Subscription ──── SubscriptionPlan
│ │
│ └── SubscriptionHistory
│
├── Post ─────────── Category
│ │
│ └── Comment ── Comment (parent/replies)
│
├── PinnedPost ───── Post
│
└── Payment ──────── Subscription
└── PaymentAttempt
└── Refund
Webhook (незалежна таблиця для Stripe events)
git clone https://github.com/whynotdimaa/newsAPI.git
cd newsAPISECRET_KEY=your-very-secret-django-key
DEBUG=False
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
# PostgreSQL
POSTGRES_DB=newssite
POSTGRES_USER=newsuser
POSTGRES_PASSWORD=your-strong-password
DB_HOST=db
DB_PORT=5432
# Stripe
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Email (опціонально)
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
DEFAULT_FROM_EMAIL=noreply@newssite.com
# Redis / Celery
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
# Frontend URL (для Stripe redirect)
FRONTEND_URL=http://localhost:5173docker-compose up --build -dDocker автоматично:
- Застосує всі міграції
- Зберере статику (Swagger/Admin)
- Запустить Gunicorn, Celery Worker та Celery Beat
docker-compose exec backend python manage.py createsuperuserdocker-compose exec backend python manage.py create_subscription_productЩоб прив'язати план до реального Stripe Price ID:
docker-compose exec backend python manage.py fix_stripe_integration| Сервіс | URL |
|---|---|
| API | http://localhost/api/v1/ |
| Swagger UI | http://localhost/api/docs/swagger/ |
| ReDoc | http://localhost/api/docs/redoc/ |
| Django Admin | http://localhost/admin/ |
| Метод | Ендпоінт | Опис | Auth |
|---|---|---|---|
| POST | /api/v1/auth/register/ |
Реєстрація нового користувача | ❌ |
| POST | /api/v1/auth/login/ |
Вхід, отримання JWT токенів | ❌ |
| POST | /api/v1/auth/logout/ |
Вихід (blacklist refresh token) | ✅ |
| GET | /api/v1/auth/profile/ |
Отримати профіль | ✅ |
| PUT/PATCH | /api/v1/auth/profile/ |
Оновити профіль | ✅ |
| PUT | /api/v1/auth/change-password/ |
Змінити пароль | ✅ |
| POST | /api/v1/auth/token/refresh |
Оновити access token | ❌ |
| Метод | Ендпоінт | Опис | Auth |
|---|---|---|---|
| GET | /api/v1/posts/ |
Стрічка постів (з закріпленими першими) | ❌ |
| POST | /api/v1/posts/ |
Створити пост | ✅ |
| GET | /api/v1/posts/{slug}/ |
Деталі поста + інкремент переглядів | ❌ |
| PUT/PATCH | /api/v1/posts/{slug}/ |
Оновити пост (лише автор) | ✅ |
| DELETE | /api/v1/posts/{slug}/ |
Видалити пост (лише автор) | ✅ |
| GET | /api/v1/posts/my-posts/ |
Мої пости (+ чернетки) | ✅ |
| GET | /api/v1/posts/popular/ |
Топ-10 за переглядами | ❌ |
| GET | /api/v1/posts/recent/ |
10 останніх постів | ❌ |
| GET | /api/v1/posts/featured/ |
3 закріплених + 6 популярних за тиждень | ❌ |
| GET | /api/v1/posts/pinned/ |
Лише закріплені пости | ❌ |
| Метод | Ендпоінт | Опис |
|---|---|---|
| GET | /api/v1/posts/categories/ |
Список категорій з кількістю постів |
| POST | /api/v1/posts/categories/ |
Створити категорію |
| GET/PUT/DELETE | /api/v1/posts/categories/{slug}/ |
CRUD категорії |
| GET | /api/v1/posts/categories/{slug}/posts/ |
Пости категорії (із закріпленими першими) |
| Метод | Ендпоінт | Опис | Auth |
|---|---|---|---|
| GET | /api/v1/comments/ |
Всі активні коментарі | ❌ |
| POST | /api/v1/comments/ |
Створити коментар або відповідь | ✅ |
| GET | /api/v1/comments/{id}/ |
Деталі коментаря з відповідями | ❌ |
| PATCH | /api/v1/comments/{id}/ |
Редагувати коментар | ✅ |
| DELETE | /api/v1/comments/{id}/ |
Soft delete коментаря | ✅ |
| GET | /api/v1/comments/my-comments/ |
Мої коментарі | ✅ |
| GET | /api/v1/comments/post/{post_id}/ |
Коментарі поста (дерево) | ❌ |
| GET | /api/v1/comments/{id}/replies/ |
Відповіді на коментар | ❌ |
| Метод | Ендпоінт | Опис | Auth |
|---|---|---|---|
| GET | /api/v1/subscribe/plans/ |
Доступні тарифні плани | ❌ |
| GET | /api/v1/subscribe/plans/{id}/ |
Деталі плану | ❌ |
| GET | /api/v1/subscribe/my-subscription/ |
Моя підписка | ✅ |
| GET | /api/v1/subscribe/status/ |
Статус підписки + чи можна закріплювати | ✅ |
| GET | /api/v1/subscribe/history/ |
Історія підписки | ✅ |
| POST | /api/v1/subscribe/cancel/ |
Відмінити підписку | ✅ |
| Метод | Ендпоінт | Опис | Auth |
|---|---|---|---|
| GET | /api/v1/subscribe/pinned-post/ |
Мій закріплений пост | ✅ |
| POST | /api/v1/subscribe/pin-post/ |
Закріпити пост | ✅ |
| POST | /api/v1/subscribe/unpin-post/ |
Відкріпити пост | ✅ |
| GET | /api/v1/subscribe/pinned-post/{post_id}/ |
Чи можна закріпити цей пост? | ✅ |
| Метод | Ендпоінт | Опис | Auth |
|---|---|---|---|
| POST | /api/v1/payment/create-checkout-session/ |
Створити Stripe Checkout сесію | ✅ |
| GET | /api/v1/payment/payments/ |
Список моїх платежів | ✅ |
| GET | /api/v1/payment/payments/{id}/ |
Деталі платежу | ✅ |
| GET | /api/v1/payment/payments/{id}/status/ |
Статус платежу (синхронізація зі Stripe) | ✅ |
| POST | /api/v1/payment/payments/{id}/cancel/ |
Скасувати платіж | ✅ |
| POST | /api/v1/payment/payments/{id}/retry/ |
Повторна спроба оплати | ✅ |
| GET | /api/v1/payment/payments/history/ |
Повна історія транзакцій | ✅ |
| POST | /api/v1/payment/webhooks/stripe/ |
Stripe webhook endpoint | ❌ |
| GET | /api/v1/payment/analytics/ |
Фінансова аналітика (лише адмін) | 🔑 |
| POST | /api/v1/payment/payments/{id}/refund/ |
Повернення коштів (лише адмін) | 🔑 |
API використовує JWT токени. Щоб отримати доступ до захищених ендпоінтів:
# 1. Отримати токени через логін
curl -X POST http://localhost/api/v1/auth/login/ \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "yourpassword"}'
# Відповідь:
# { "access": "eyJ...", "refresh": "eyJ..." }
# 2. Використовувати access токен у запитах
curl http://localhost/api/v1/posts/my-posts/ \
-H "Authorization: Bearer eyJ..."Час життя токенів:
- Access Token: 60 хвилин
- Refresh Token: 7 днів (автооновлення при ротації)
Проект використовує pytest + pytest-django:
# Запустити всі тести
docker-compose exec backend pytest
# З детальним виводом
docker-compose exec backend pytest -v
# Лише певний модуль
docker-compose exec backend pytest tests/test_posts.py
docker-compose exec backend pytest tests/test_subscribe.pyПокриття тестами:
tests/test_accounts.py— реєстрація, логін, профіль, зміна пароляtests/test_posts.py— CRUD постів, права доступу, лічильник переглядівtests/test_comments.py— коментарі, відповіді, soft deletetests/test_subscribe.py— підписки, закріплення/відкріплення постів
| Задача | Файл | Розклад |
|---|---|---|
| Перевірка прострочених підписок | subscribe/tasks.py |
Щогодини |
| Email нагадування (3 дні до кінця) | subscribe/tasks.py |
Щодня |
| Очищення старих платежів (90+ днів) | payment/tasks.py |
Щотижня |
| Очищення старих webhook-подій | payment/tasks.py |
Щодня |
| Повторна обробка невдалих webhook | payment/tasks.py |
Щогодини |
Django Admin (/admin/) надає повний контроль над усіма моделями:
- Користувачі — пошук, фільтрація, права доступу
- Пости та категорії — модерація, попередньо заповнений slug
- Коментарі — масові дії (активувати/деактивувати)
- Підписки — активація/відміна/протермінування одним кліком
- Платежі — кольоровий статус, посилання на підписку, retry webhook
- Refunds — повне та часткове повернення коштів