Skip to content
This repository was archived by the owner on Jul 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
02fae1b
#145: add auto-restart to containers
imbeer May 11, 2025
1b5fee9
#83: refactor task and user views and services
imbeer May 19, 2025
bf9abf0
#83: refactor statistics
Soopcha May 21, 2025
d2cbf4b
#83: refactor statistics
Soopcha May 21, 2025
e70f9c8
merge pull request from AlexanderLaptev/refactor#83
imbeer May 22, 2025
a77704c
clean up project
imbeer May 22, 2025
17ca5af
#143: add csrf trusted origins to settings.py
imbeer May 22, 2025
855b488
#143: add nginx to docker compose and hide application and database p…
imbeer May 22, 2025
9cbb507
clean up pycache
imbeer May 22, 2025
544886d
merge pull request from AlexanderLaptev/feature#143
imbeer May 22, 2025
d7387d0
remove debug print
imbeer May 22, 2025
04b062d
fix 204 content
imbeer May 22, 2025
57ff7f0
fix nginx configuration
imbeer May 22, 2025
4de6088
merge pull request from AlexanderLaptev/feature#143
imbeer May 22, 2025
bc0d095
change build test port
imbeer May 22, 2025
ed7be90
fix django allowed host
imbeer May 22, 2025
ea57270
merge pull request #157 AlexanderLaptev/feature#143
imbeer May 22, 2025
fc1388f
fix copy-action arguments
imbeer May 22, 2025
048f4e2
#143: fix copy-action arguments
imbeer May 22, 2025
a229c1e
fix deploy.yaml
imbeer May 22, 2025
80d74d6
Merge branch 'backend-django' into feature#143
imbeer May 22, 2025
589e529
merge pull request from AlexanderLaptev/feature#143
imbeer May 22, 2025
0a4c081
fix nginx port
imbeer May 22, 2025
d1e8817
merge pull request from AlexanderLaptev/feature#143
imbeer May 22, 2025
b9580db
add database ports for local tests
imbeer May 22, 2025
546d376
#162: fix category update
imbeer May 22, 2025
d7070f4
merge pull request from AlexanderLaptev/feature#162
imbeer May 22, 2025
df72ccb
#73: add yookassa dependencies
imbeer May 22, 2025
b749037
#73: add yookassa env variables
imbeer May 22, 2025
56149da
#73: implement base yookassa API
imbeer May 22, 2025
ba2ff01
#149: add locustfile
Soopcha May 23, 2025
47bb76c
#149: add locustfile
Soopcha May 23, 2025
8cce3ca
merge pull request from AlexanderLaptev/feature#149
imbeer May 23, 2025
0d18199
#111: update category
Soopcha May 27, 2025
c95f2fa
merge pull request from AlexanderLaptev/feature#111
imbeer May 27, 2025
70e992b
#73: make migrations
imbeer May 25, 2025
aaceb3c
expose ports of database for tests
imbeer May 25, 2025
856edbd
#73: fix payment creation
imbeer May 27, 2025
d0cf05a
#73: add scheduler (not tested yet)
imbeer May 27, 2025
5c69533
#73: add subscription API (not tested yet)
imbeer May 27, 2025
959bb95
#73: test payment system [0]
imbeer May 27, 2025
662fa01
#73: update dashboard statistics
imbeer May 27, 2025
651b672
#73: try to fix some bugs
imbeer May 27, 2025
bf812ef
#73: test payment system [1]
imbeer May 27, 2025
bcedf2d
#73: update actions secrets
imbeer May 27, 2025
58054cc
#73: try to fix webhook handling
imbeer May 27, 2025
4af7c9b
#73: test payment system [2]
imbeer May 27, 2025
ee0f8eb
#73: try to fix subscription id
imbeer May 27, 2025
9bd270e
#73: test payment system [3]
imbeer May 27, 2025
69458f2
#73: fix a typo
imbeer May 27, 2025
b3e46bb
#73: fix a typo
imbeer May 27, 2025
309adf0
#73: add ip check
imbeer May 27, 2025
5e48054
#73: add ip check
imbeer May 27, 2025
cbe44b2
#73: remove ipv6 for now
imbeer May 27, 2025
75cade1
merge pull request from AlexanderLaptev/payment-system
imbeer May 27, 2025
9f33537
#73: revert ip check
imbeer May 27, 2025
d24530f
merge pull request from AlexanderLaptev/payment-system
imbeer May 27, 2025
3c93237
#73: fix user status API
imbeer May 27, 2025
35f0118
merge pull request from AlexanderLaptev/payment-system
imbeer May 27, 2025
4dfed7a
#73: fix user status API [1]
imbeer May 27, 2025
6750a35
merge pull request from AlexanderLaptev/payment-system
imbeer May 27, 2025
61ab72b
#73: add subscription tests
imbeer May 28, 2025
21d478c
#73: clean up previous fake subscription API
imbeer May 28, 2025
57dae1c
merge pull request from AlexanderLaptev/payment-system
imbeer May 28, 2025
197b1bc
turn off one broken annoying statistics test
imbeer May 28, 2025
8d5ecc7
merge pull request from AlexanderLaptev/hotfix-test
imbeer May 28, 2025
b191cb1
#131: refactor and move suggestion service to another directory
imbeer May 29, 2025
f84e879
#131: improve system prompts and add subscription check
imbeer May 29, 2025
c1fd2b9
#131: Improve suggestion service
imbeer May 29, 2025
ed6366d
#131: fix empty time generation error
imbeer May 30, 2025
0f97e2e
ьerge pull request from AlexanderLaptev/feature#131
imbeer May 30, 2025
a7d15a7
fix yookassa method id and time return
imbeer May 30, 2025
c920fad
merge pull request from AlexanderLaptev/fix-payment-system
imbeer May 30, 2025
5b3db41
try to fix yookassa payment id
imbeer May 31, 2025
086930f
merge pull request from AlexanderLaptev/fix-payment-system
imbeer May 31, 2025
2363697
#198: add payment_return_page
Soopcha Jun 1, 2025
4cb2213
merge pull request from AlexanderLaptev/feature#198
imbeer Jun 2, 2025
04578bd
#196: add backup service
imbeer Jun 4, 2025
bd5d62a
update README.md
imbeer Jun 4, 2025
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
13 changes: 9 additions & 4 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
DATABASE_HOST=database
DATABASE_PORT=5432
GIGACHAT_AUTH_KEY=${{ secrets.GIGACHAT_AUTH_KEY }}
YOOKASSA_AUTH_KEY=${{ secrets.YOOKASSA_AUTH_KEY }}
YOOKASSA_STORE_ID=${{ secrets.YOOKASSA_STORE_ID }}
EOF

- name: Local deploy for test
Expand Down Expand Up @@ -85,6 +87,9 @@ jobs:
DATABASE_HOST=database
DATABASE_PORT=5432
GIGACHAT_AUTH_KEY=${{ secrets.GIGACHAT_AUTH_KEY }}
YOOKASSA_AUTH_KEY=${{ secrets.YOOKASSA_AUTH_KEY }}
YOOKASSA_STORE_ID=${{ secrets.YOOKASSA_STORE_ID }}
SERVER_HOST=${{ secrets.SERVER_HOST }}
EOF

- name: Log in to Docker hub
Expand All @@ -103,7 +108,7 @@ jobs:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_PASSWORD }}
source: backend/docker-compose.yaml, backend/.env
source: backend/docker-compose.yaml, backend/.env, backend/nginx.conf
target: /home/${{ secrets.SERVER_USER }}/app/

- name: Deploy
Expand All @@ -123,10 +128,10 @@ jobs:
- name: Check if app is responding on port
run: |
echo "Waiting for server to be reachable..."
for i in {1..30}; do
nc -zv ${{ secrets.SERVER_HOST }} 8000 && echo "OK: Port is open!" && exit 0
for i in {1..10}; do
nc -zv ${{ secrets.SERVER_HOST }} 80 && echo "OK: Port is open!" && exit 0
echo "Attempt $i: Server not ready yet..."
sleep 10
done
echo "ERROR: Server is not responding on port 8000"
echo "ERROR: Server is not responding on port 80"
exit 1
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
DATABASE_HOST=database
DATABASE_PORT=5432
GIGACHAT_AUTH_KEY=${{ secrets.GIGACHAT_AUTH_KEY }}
YOOKASSA_AUTH_KEY=${{ secrets.YOOKASSA_AUTH_KEY }}
YOOKASSA_STORE_ID=${{ secrets.YOOKASSA_STORE_ID }}
EOF

- name: Local deploy for test
Expand Down
6 changes: 6 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__/
*.pyc
*.log
.env
venv/
.git/
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.idea
.env
**/__pycache__/
**.crt
**.key
**/nginx-logs/
18 changes: 16 additions & 2 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@
> DATABASE_PASSWORD - пароль от базы данных

> GIGACHAT_AUTH_KEY - API ключ от Gigachat
- Используются в github actions

> YOOKASSA_AUTH_KEY - API ключ от ЮKassa

> YOOKASSA_STORE_ID - идентификатор от магазина в ЮKassa

> SERVER_HOST - ip сервера

- Используются в github actions
> SERVER_USER - ssh пользователь

> SERVER_PASSWORD - пароль от ssh пользователя
Expand Down Expand Up @@ -48,4 +53,13 @@
или
```
docker logs taskbench-backend -f
```
```

# Создание резервных копий базы данных
Версия postgresql: 17

В директории [/scripts/database_backup_service](https://github.com/AlexanderLaptev/Taskbench/tree/backend-django/backend/scripts/database_backup_service) находится bash-скрипт, добавляющий задачу создания резервных копий в crontab.

`install.sh` - установка задачи
`restore.sh` - восстановление резервной копии
`uninstall.sh` - удаление задачи, очистка сохраненных копий.
Binary file removed backend/backend/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file removed backend/backend/__pycache__/settings.cpython-312.pyc
Binary file not shown.
Binary file removed backend/backend/__pycache__/urls.cpython-312.pyc
Binary file not shown.
Binary file removed backend/backend/__pycache__/wsgi.cpython-312.pyc
Binary file not shown.
9 changes: 0 additions & 9 deletions backend/backend/asgi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
"""
ASGI config for backend project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application
Expand Down
64 changes: 16 additions & 48 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,23 @@
"""
Django settings for backend project.

Generated by 'django-dashboard startproject' using Django 5.2.

For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import os
from datetime import timedelta
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
# SECRET_KEY = ''
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
GIGACHAT_KEY = os.environ.get("GIGACHAT_AUTH_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = bool(os.environ.get("DEBUG", default=True))

SERVER_HOST = os.environ.get("SERVER_HOST", default="127.0.0.1")
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "127.0.0.1").split(",")

# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_apscheduler',
'taskbench.apps.TaskbenchConfig',
'rest_framework',
'rest_framework_simplejwt',
Expand Down Expand Up @@ -70,10 +51,14 @@
},
]

WSGI_APPLICATION = 'backend.wsgi.application'
CSRF_TRUSTED_ORIGINS = [
"https://" + SERVER_HOST,
"https://" + SERVER_HOST + ":443",
]
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
WSGI_APPLICATION = 'backend.wsgi.application'

DATABASES = {
'default': {
Expand All @@ -86,12 +71,6 @@
}
}

# REST_FRAMEWORK = {
# 'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework_simplejwt.authentication.JWTAuthentication',
# )
# }

SIMPLE_JWT = {
'USER_ID_FIELD': 'user_id',
'USER_ID_CLAIM': 'user_id',
Expand All @@ -106,9 +85,6 @@
'ISSUER': None,
}

# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
Expand All @@ -127,7 +103,6 @@
},
]


LOGGING = {
'version': 1,
'disable_existing_loggers': False,
Expand Down Expand Up @@ -166,24 +141,17 @@
}
}


# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
APSCHEDULER_DATETIME_FORMAT = "N j, Y, f:s a"
APSCHEDULER_RUN_NOW_TIMEOUT = 25

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

SUBSCRIPTION_PRICE = 149.00
SUBSCRIPTION_CURRENCY = "RUB"
YOOKASSA_STORE_ID = os.environ.get("YOOKASSA_STORE_ID")
YOOKASSA_AUTH_KEY = os.environ.get("YOOKASSA_AUTH_KEY")
39 changes: 11 additions & 28 deletions backend/backend/urls.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,39 @@
"""
URL configuration for backend project.

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

from taskbench.views.statisctics_views import StatisticsView
from taskbench.views.suggestion_views import SuggestionView
from taskbench.views.category_views import CategoryListView, CategoryDetailView
from taskbench.views.statistics_views import StatisticsView
from taskbench.views.subtask_views import (
SubtaskCreateView,
SubtaskDetailView
)
from taskbench.views.task_views import (
TaskListView,
TaskDetailView,
SubtaskCreateView,
SubtaskDetailView,
CategoryListView
)
from taskbench.views.user_views import (
RegisterView,
LoginView,
DeleteUserView,
TokenRefreshView,
ChangePasswordView,
SubscriptionStatusView,
CreateSubscriptionView
ChangePasswordView
)


urlpatterns = [
path("", include("dashboard.urls")),
path("", include("subscription.urls")),
path("", include("suggestion.urls")),
path('tasks/', TaskListView.as_view(), name='task_list'),
path('tasks/<int:task_id>/', TaskDetailView.as_view(), name='task_detail'),
path('subtasks/', SubtaskCreateView.as_view(), name='subtask_create'),
path('subtasks/<int:subtask_id>/', SubtaskDetailView.as_view(), name='subtask_detail'),
path('categories/', CategoryListView.as_view(), name='categories'),
path('categories/<int:category_id>/', CategoryDetailView.as_view(), name='category_detail'),
path('user/register/', RegisterView.as_view(), name='register'), # POST - создание пользователя
path('user/login/', LoginView.as_view(), name='login'), # POST - валидация пользователя, возвращение jwt
path('user/delete/', DeleteUserView.as_view(), name='delete_user'), # POST - валидация пользователя, возвращение jwt
path('user/password/', ChangePasswordView.as_view(), name='change_password'),
path('token/refresh/', TokenRefreshView.as_view(), name="token_refresh"),
path('statistics/', StatisticsView.as_view(), name='statistics'),
path('ai/suggestions/', SuggestionView.as_view(), name="ai_suggestions"),
path('user/subscription/status/', SubscriptionStatusView.as_view()),
path('user/subscription/', CreateSubscriptionView.as_view()),
]

9 changes: 0 additions & 9 deletions backend/backend/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
"""
WSGI config for backend project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application
Expand Down
2 changes: 1 addition & 1 deletion backend/dashboard/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
path("admin/login/", custom_login, name="custom_login"),
path("admin/dashboard/", dashboard_view, name="dashboard"),
path("admin/subscriptions/", subscription_page, name="admin_subscriptions"),
path("admin/api/stats/", stats_api, name="stats_api"),
path("admin/dashboard/stats/", stats_api, name="stats_api"),
path("admin/api/subscriptions/", subscription_list_api, name="subscription_list_api"),
]
50 changes: 29 additions & 21 deletions backend/dashboard/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from datetime import timedelta
from django.utils import timezone
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.paginator import Paginator
from django.shortcuts import render, redirect
from django.http import HttpResponseForbidden, JsonResponse
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_protect

from taskbench.models.models import Subscription
from taskbench.models.models import Subscription, Subtask, Task, User


@csrf_protect
Expand Down Expand Up @@ -35,21 +37,27 @@ def subscription_page(request):

@user_passes_test(lambda u: u.is_staff)
def stats_api(request):
data = {
"total_users": 280,
"total_subscribers": 28,
"new_users_week": 40,
"new_users_today": 10,
"new_subs_week": 6,
"new_subs_today": 2,
"active_users_week": 144,
"active_users_today": 67,
"total_tasks": 20509,
"tasks_week": 1563,
"tasks_today": 250,
"total_subtasks": 48524,
}
return JsonResponse(data)
if request.method == "GET":
now = timezone.now()
today = now.date()
start_of_week = now - timedelta(days=now.weekday())
start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0)

data = {
"total_users": User.objects.all().count(),
"total_subscribers": Subscription.objects.values('user').distinct().count(),
"new_users_week": User.objects.filter(created_at__gte=start_of_week).count(),
"new_users_today": User.objects.filter(created_at__date=today).count(),
"new_subs_week": Subscription.objects.filter(start_date__gt=start_of_week).count(),
"new_subs_today": Subscription.objects.filter(start_date__date=today).count(),
"active_users_week": User.objects.filter(access_at__gt=start_of_week).count(),
"active_users_today": User.objects.filter(access_at__date=today).count(),
"total_tasks": Task.objects.all().count(),
"tasks_week": Task.objects.filter(created_at__gte=start_of_week).count(),
"tasks_today": Task.objects.filter(created_at__date=today).count(),
"total_subtasks": Subtask.objects.all().count(),
}
return JsonResponse(data)

@user_passes_test(lambda u: u.is_staff)
def subscription_list_api(request):
Expand All @@ -65,10 +73,10 @@ def subscription_list_api(request):
{
"id": sub.subscription_id,
"email": sub.user.email,
"start_date": sub.start_date.strftime("%Y-%m-%d"),
"end_date": sub.end_date.strftime("%Y-%m-%d"),
"start_date": sub.start_date.strftime("%Y-%m-%d") if sub.start_date else "N/A",
"end_date": sub.end_date.strftime("%Y-%m-%d") if sub.end_date else "N/A",
"is_active": sub.is_active,
"transaction_id": sub.transaction_id or "-",
"transaction_id": sub.latest_yookassa_payment_id or "-",
}
for sub in page.object_list
],
Expand Down
Loading