start auth apis and packages, redis, celery and run with docker
This commit is contained in:
13
config/celery.py
Normal file
13
config/celery.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import os
|
||||
|
||||
import celery
|
||||
|
||||
from config.env import env
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE"))
|
||||
|
||||
app = celery.Celery("config")
|
||||
|
||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
||||
|
||||
app.autodiscover_tasks()
|
||||
7
config/conf/celery.py
Normal file
7
config/conf/celery.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.conf import settings
|
||||
from config.env import env
|
||||
|
||||
CELERY_BROKER_URL = env.str('REDIS_URL')
|
||||
CELERY_ACCEPT_CONTENT = ['json']
|
||||
CELERY_TASK_SERIALIZER = 'json'
|
||||
CELERY_TIMEZONE = settings.TIME_ZONE
|
||||
7
config/conf/drf_spectacular.py
Normal file
7
config/conf/drf_spectacular.py
Normal file
@@ -0,0 +1,7 @@
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"TITLE": "Your Project API",
|
||||
"DESCRIPTION": "Your project description",
|
||||
"VERSION": "1.0.0",
|
||||
"SERVE_INCLUDE_SCHEMA": False,
|
||||
"CAMELIZE_NAMES": True,
|
||||
}
|
||||
6
config/conf/rest_framework.py
Normal file
6
config/conf/rest_framework.py
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTAuthentication",),
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
|
||||
}
|
||||
36
config/conf/simplejwt.py
Normal file
36
config/conf/simplejwt.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from config.env import env
|
||||
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
|
||||
"ROTATE_REFRESH_TOKENS": False,
|
||||
"BLACKLIST_AFTER_ROTATION": False,
|
||||
"UPDATE_LAST_LOGIN": False,
|
||||
"ALGORITHM": "HS256",
|
||||
"SIGNING_KEY": env("SECRET_KEY"),
|
||||
"VERIFYING_KEY": "",
|
||||
"AUDIENCE": None,
|
||||
"ISSUER": None,
|
||||
"JSON_ENCODER": None,
|
||||
"JWK_URL": None,
|
||||
"LEEWAY": 0,
|
||||
"AUTH_HEADER_TYPES": ("Bearer",),
|
||||
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
|
||||
"USER_ID_FIELD": "id",
|
||||
"USER_ID_CLAIM": "user_id",
|
||||
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
|
||||
"TOKEN_TYPE_CLAIM": "token_type",
|
||||
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
|
||||
"JTI_CLAIM": "jti",
|
||||
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
|
||||
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=60),
|
||||
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=30),
|
||||
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
|
||||
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
|
||||
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
|
||||
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
|
||||
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
|
||||
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
|
||||
}
|
||||
@@ -34,7 +34,9 @@ APPS = [
|
||||
]
|
||||
|
||||
PACKAGES = [
|
||||
|
||||
'drf_spectacular',
|
||||
'rest_framework',
|
||||
'rest_framework_simplejwt',
|
||||
]
|
||||
|
||||
INSTALLED_APPS = []
|
||||
@@ -131,3 +133,10 @@ MEDIA_ROOT = BASE_DIR / 'resources/media/'
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
AUTH_USER_MODEL = 'accounts.User'
|
||||
|
||||
from config.conf.drf_spectacular import *
|
||||
from config.conf.rest_framework import *
|
||||
from config.conf.simplejwt import *
|
||||
from config.conf.celery import *
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
"""
|
||||
URL configuration for config 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
|
||||
from django.urls import path, include
|
||||
|
||||
from drf_spectacular.views import SpectacularSwaggerView, SpectacularAPIView
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
|
||||
path('api/v1/', include(
|
||||
[
|
||||
path('', include('core.apps.accounts.urls')),
|
||||
]
|
||||
)),
|
||||
# swagger
|
||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from .user import *
|
||||
from .verification_code import *
|
||||
45
core/apps/accounts/admins/user.py
Normal file
45
core/apps/accounts/admins/user.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as CustomUserAdmin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.apps.accounts.models.user import User
|
||||
|
||||
admin.site.unregister(Group)
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(CustomUserAdmin):
|
||||
fieldsets = (
|
||||
(None, {"fields": ("phone", "password")}),
|
||||
(_("Personal info"), {"fields": ("first_name", "last_name", "email", 'role')}),
|
||||
(
|
||||
_("Permissions"),
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
),
|
||||
},
|
||||
),
|
||||
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": ("phone", "usable_password", "password1", "password2"),
|
||||
},
|
||||
),
|
||||
)
|
||||
list_display = ("phone", "email", "first_name", "last_name", "is_staff")
|
||||
list_filter = ("is_staff", "is_superuser", "is_active", "groups")
|
||||
search_fields = ("phone", "first_name", "last_name", "email")
|
||||
ordering = ("phone",)
|
||||
filter_horizontal = (
|
||||
"groups",
|
||||
"user_permissions",
|
||||
)
|
||||
8
core/apps/accounts/admins/verification_code.py
Normal file
8
core/apps/accounts/admins/verification_code.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from core.apps.accounts.models.verification_code import VerificationCode
|
||||
|
||||
@admin.register(VerificationCode)
|
||||
class VerificationCodeAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'user', 'code', 'is_expired', 'is_verify']
|
||||
|
||||
@@ -4,3 +4,6 @@ from django.apps import AppConfig
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core.apps.accounts'
|
||||
|
||||
def ready(self):
|
||||
import core.apps.accounts.admins
|
||||
0
core/apps/accounts/enums/__init__.py
Normal file
0
core/apps/accounts/enums/__init__.py
Normal file
7
core/apps/accounts/enums/user.py
Normal file
7
core/apps/accounts/enums/user.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
ROLE_CHOICES = (
|
||||
('PP', 'physical person'),
|
||||
('LP', 'legal person')
|
||||
)
|
||||
@@ -19,3 +19,14 @@ class BaseUserManager(UserManager):
|
||||
extra_fields.setdefault("is_staff", False)
|
||||
extra_fields.setdefault("is_superuser", False)
|
||||
return self._create_user(phone, password, **extra_fields)
|
||||
|
||||
def create_superuser(self, phone, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
|
||||
if extra_fields.get("is_staff") is not True:
|
||||
raise ValueError("Superuser must have is_staff=True.")
|
||||
if extra_fields.get("is_superuser") is not True:
|
||||
raise ValueError("Superuser must have is_superuser=True.")
|
||||
|
||||
return self._create_user(phone, password, **extra_fields)
|
||||
|
||||
50
core/apps/accounts/migrations/0001_initial.py
Normal file
50
core/apps/accounts/migrations/0001_initial.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Generated by Django 5.2 on 2025-07-14 15:16
|
||||
|
||||
import core.apps.accounts.managers.user
|
||||
import django.core.validators
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('phone', models.CharField(max_length=13, unique=True, validators=[django.core.validators.RegexValidator(message="Telefon raqam formatda bo'lishi kerak: +998XXXXXXXXX", regex='^\\+998\\d{9}$')])),
|
||||
('role', models.CharField(choices=[('PP', 'physical person'), ('LP', 'legal person')], max_length=2)),
|
||||
('indentification_num', models.CharField(blank=True, max_length=14, null=True)),
|
||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='users/profile_image/%Y/%m/')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'db_table': 'users',
|
||||
},
|
||||
managers=[
|
||||
('objects', core.apps.accounts.managers.user.BaseUserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
33
core/apps/accounts/migrations/0002_verificationcode.py
Normal file
33
core/apps/accounts/migrations/0002_verificationcode.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.2 on 2025-07-14 17:08
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VerificationCode',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('code', models.PositiveIntegerField()),
|
||||
('is_expired', models.BooleanField(default=False)),
|
||||
('is_verify', models.BooleanField(default=False)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='verification_codes', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Verification Code',
|
||||
'verbose_name_plural': 'Verification Codes',
|
||||
'db_table': 'verification_codes',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2 on 2025-07-14 17:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0002_verificationcode'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='verificationcode',
|
||||
name='expiration_time',
|
||||
field=models.TimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
from .user import *
|
||||
from .verification_code import *
|
||||
41
core/apps/accounts/models/user.py
Normal file
41
core/apps/accounts/models/user.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import random, json, datetime
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.utils import timezone
|
||||
|
||||
from core.apps.accounts.managers.user import BaseUserManager
|
||||
from core.apps.accounts.enums.user import ROLE_CHOICES
|
||||
from core.apps.accounts.validators.user import phone_regex
|
||||
from core.apps.accounts.models.verification_code import VerificationCode
|
||||
from core.apps.shared.models.base import BaseModel
|
||||
|
||||
|
||||
class User(BaseModel, AbstractUser):
|
||||
phone = models.CharField(max_length=13, validators=[phone_regex], unique=True)
|
||||
role = models.CharField(max_length=2, choices=ROLE_CHOICES)
|
||||
indentification_num = models.CharField(max_length=14, null=True, blank=True)
|
||||
profile_image = models.ImageField(upload_to='users/profile_image/%Y/%m/', null=True, blank=True)
|
||||
|
||||
objects = BaseUserManager()
|
||||
username = None
|
||||
USERNAME_FIELD = 'phone'
|
||||
REQUIRED_FIELDS = []
|
||||
|
||||
def __str__(self):
|
||||
return self.phone
|
||||
|
||||
def generate_code(self):
|
||||
code = ''.join([str(random.randint(0, 100) % 10) for _ in range(4)])
|
||||
expiration_time = timezone.now() + datetime.timedelta(minutes=2)
|
||||
VerificationCode.objects.create(
|
||||
code=code,
|
||||
user=self,
|
||||
expiration_time=expiration_time,
|
||||
)
|
||||
return code
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'user'
|
||||
verbose_name_plural = 'users'
|
||||
db_table = 'users'
|
||||
18
core/apps/accounts/models/verification_code.py
Normal file
18
core/apps/accounts/models/verification_code.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
from core.apps.shared.models.base import BaseModel
|
||||
|
||||
class VerificationCode(BaseModel):
|
||||
code = models.PositiveIntegerField()
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='verification_codes')
|
||||
is_expired = models.BooleanField(default=False)
|
||||
is_verify = models.BooleanField(default=False)
|
||||
expiration_time = models.TimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.phone} - {self.code}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Verification Code'
|
||||
verbose_name_plural = 'Verification Codes'
|
||||
db_table = 'verification_codes'
|
||||
42
core/apps/accounts/serializers/auth.py
Normal file
42
core/apps/accounts/serializers/auth.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from django.db import transaction
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
phone = serializers.CharField()
|
||||
password = serializers.CharField()
|
||||
|
||||
def validate(self, data):
|
||||
try:
|
||||
user = User.objects.get(phone=data.get('phone'))
|
||||
except User.DoesNotExist:
|
||||
raise serializers.ValidationError({'detail': 'User not found'})
|
||||
else:
|
||||
if not user.check_password(data.get('password')):
|
||||
raise serializers.ValidationError({'detail': 'User not found'})
|
||||
data['user'] = user
|
||||
return data
|
||||
|
||||
|
||||
class RegisterSerializer(serializers.Serializer):
|
||||
phone = serializers.CharField()
|
||||
password = serializers.CharField()
|
||||
|
||||
def validate(self, data):
|
||||
if User.objects.filter(phone=data.get('phone')).exists():
|
||||
raise serializers.ValidationError({'detail': "User with this phone number already exists"})
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
with transaction.atomic():
|
||||
new_user = User.objects.create_user(
|
||||
phone=validated_data.pop('phone'),
|
||||
)
|
||||
new_user.set_password(validated_data.pop('password'))
|
||||
new_user.save()
|
||||
return new_user
|
||||
|
||||
|
||||
0
core/apps/accounts/tasks/__init__.py
Normal file
0
core/apps/accounts/tasks/__init__.py
Normal file
9
core/apps/accounts/tasks/user.py
Normal file
9
core/apps/accounts/tasks/user.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from celery import shared_task
|
||||
|
||||
from core.apps.accounts.models.verification_code import VerificationCode
|
||||
from core.services.sms import send_sms_eskiz
|
||||
|
||||
@shared_task
|
||||
def create_and_send_sms_code(user):
|
||||
code = user.generate_code()
|
||||
send_sms_eskiz(user.phone, code)
|
||||
@@ -1 +1,12 @@
|
||||
from django.urls import path
|
||||
from django.urls import path, include
|
||||
|
||||
from core.apps.accounts.views.auth import LoginApiView, RegisterApiView
|
||||
|
||||
urlpatterns = [
|
||||
path('auth/', include(
|
||||
[
|
||||
path('login/', LoginApiView.as_view(), name='login'),
|
||||
path('register/', RegisterApiView.as_view(), name='login'),
|
||||
]
|
||||
))
|
||||
]
|
||||
0
core/apps/accounts/validators/__init__.py
Normal file
0
core/apps/accounts/validators/__init__.py
Normal file
6
core/apps/accounts/validators/user.py
Normal file
6
core/apps/accounts/validators/user.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.core.validators import RegexValidator
|
||||
|
||||
phone_regex = RegexValidator(
|
||||
regex=r'^\+998\d{9}$',
|
||||
message="Telefon raqam formatda bo'lishi kerak: +998XXXXXXXXX"
|
||||
)
|
||||
32
core/apps/accounts/views/auth.py
Normal file
32
core/apps/accounts/views/auth.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from rest_framework import generics, status, views
|
||||
from rest_framework.response import Response
|
||||
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from core.apps.accounts.serializers import auth as auth_serializer
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@extend_schema(tags=['auth'])
|
||||
class LoginApiView(generics.GenericAPIView):
|
||||
serializer_class = auth_serializer.LoginSerializer
|
||||
queryset = User.objects.all()
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid(raise_exception=True):
|
||||
user = serializer.validated_data.get('user')
|
||||
tokens = RefreshToken.for_user(user)
|
||||
return Response({'access_token': str(tokens.access_token), 'refresh_token': str(tokens), 'role': user.role}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@extend_schema(tags=['auth'])
|
||||
class RegisterApiView(generics.CreateAPIView):
|
||||
serializer_class = auth_serializer.RegisterSerializer
|
||||
queryset = User.objects.all()
|
||||
permission_classes = []
|
||||
@@ -0,0 +1 @@
|
||||
from .base import *
|
||||
12
core/apps/shared/models/base.py
Normal file
12
core/apps/shared/models/base.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
id = models.UUIDField(editable=False, primary_key=True, unique=True, default=uuid.uuid4)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
0
core/services/__init__.py
Normal file
0
core/services/__init__.py
Normal file
22
core/services/sms.py
Normal file
22
core/services/sms.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from config.env import env
|
||||
|
||||
import requests
|
||||
|
||||
def send_sms_eskiz(phone, code):
|
||||
login_url = "https://notify.eskiz.uz/api/auth/login"
|
||||
token_res = requests.post(login_url, json={
|
||||
"email": env("ESKIZ_EMAIL"),
|
||||
"password": env("ESKIZ_PASSWORD")
|
||||
})
|
||||
token = token_res.json()['data']['token']
|
||||
|
||||
sms_url = "https://notify.eskiz.uz/api/message/sms/send"
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
data = {
|
||||
"mobile_phone": phone,
|
||||
"message": f"Sizning tasdiqlash kodingiz: {code}",
|
||||
"from": "4546"
|
||||
}
|
||||
|
||||
response = requests.post(sms_url, headers=headers, json=data)
|
||||
return response.json()
|
||||
@@ -55,4 +55,20 @@ services:
|
||||
networks:
|
||||
- trustme
|
||||
restart: always
|
||||
image: redis
|
||||
image: redis:latest
|
||||
|
||||
celery:
|
||||
networks:
|
||||
- trustme
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/Dockerfile.web
|
||||
command: celery -A config worker --loglevel=info
|
||||
volumes:
|
||||
- .:/code
|
||||
depends_on:
|
||||
- redis
|
||||
- web
|
||||
environment:
|
||||
CELERY_BROKER_URL: ${REDIS_URL}
|
||||
|
||||
|
||||
@@ -2,4 +2,12 @@ django==5.2
|
||||
gunicorn
|
||||
uvicorn
|
||||
psycopg2
|
||||
django-environ==0.12.0
|
||||
django-environ==0.12.0
|
||||
pillow
|
||||
drf-spectacular==0.28.0
|
||||
djangorestframework_simplejwt==5.5.0
|
||||
djangorestframework
|
||||
requests
|
||||
celery==5.5.3
|
||||
redis==6.2.0
|
||||
django-redis==6.0.0
|
||||
Reference in New Issue
Block a user