Initial commit: React + Django full-stack project setup

- Backend: Django 4.2 + DRF + JWT + GraphQL
- Frontend: React 18 + MobX + styled-components
- Deployment: Docker + Docker Compose + Nginx
- Database: PostgreSQL support
- Documentation: README, INIT, PROJECT_DOCS, TESTING
This commit is contained in:
mashen
2026-04-09 12:06:14 +00:00
commit cb491e8b87
49 changed files with 1804 additions and 0 deletions

1
backend/apps/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Apps package

View File

@@ -0,0 +1 @@
# API app

6
backend/apps/api/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.api'

View File

@@ -0,0 +1,7 @@
from django.urls import path
from graphene_django.views import GraphQLView
from apps.api.schema import schema
urlpatterns = [
path('', GraphQLView.as_view(graphiql=True, schema=schema)),
]

View File

@@ -0,0 +1,31 @@
import graphene
from graphene_django import DjangoObjectType
from apps.users.models import User
class UserType(DjangoObjectType):
"""GraphQL type for User."""
class Meta:
model = User
fields = '__all__'
class Query(graphene.ObjectType):
"""Root GraphQL Query."""
all_users = graphene.List(UserType)
me = graphene.Field(UserType)
def resolve_all_users(root, info):
"""Resolve all users query."""
return User.objects.all()
def resolve_me(root, info):
"""Resolve current user query."""
if info.context.user.is_authenticated:
return info.context.user
return None
schema = graphene.Schema(query=Query)

View File

@@ -0,0 +1,29 @@
from rest_framework import serializers
from django.contrib.auth import get_user_model
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
User = get_user_model()
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
"""Custom JWT token serializer that includes user data."""
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['email'] = user.email
token['username'] = user.username
return token
def validate(self, attrs):
data = super().validate(attrs)
# Add user data to response
data['user'] = {
'id': self.user.id,
'email': self.user.email,
'username': self.user.username,
'first_name': self.user.first_name,
'last_name': self.user.last_name,
}
return data

12
backend/apps/api/urls.py Normal file
View File

@@ -0,0 +1,12 @@
from django.urls import path, include
from rest_framework_simplejwt.views import (
TokenRefreshView,
)
from apps.users.urls import urlpatterns as users_urls
from apps.api.views import CustomTokenObtainPairView
urlpatterns = [
path('auth/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('auth/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('', include(users_urls)),
]

11
backend/apps/api/views.py Normal file
View File

@@ -0,0 +1,11 @@
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer
class CustomTokenObtainPairView(TokenObtainPairView):
"""Custom token view that returns user data with tokens."""
serializer_class = CustomTokenObtainPairSerializer

View File

@@ -0,0 +1 @@
# Core app

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.core'

View File

@@ -0,0 +1 @@
# Users app

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.users'

View File

@@ -0,0 +1,22 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
"""Custom user model extending AbstractUser."""
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name']
class Meta:
db_table = 'users'
verbose_name = 'User'
verbose_name_plural = 'Users'
def __str__(self):
return self.email

View File

@@ -0,0 +1,20 @@
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
"""Serializer for User model."""
class Meta:
model = User
fields = ('id', 'username', 'email', 'first_name', 'last_name', 'avatar')
read_only_fields = ('id',)
class UserDetailSerializer(serializers.ModelSerializer):
"""Detailed serializer for User model."""
class Meta:
model = User
fields = '__all__'
read_only_fields = ('id', 'date_joined', 'last_login')

View File

@@ -0,0 +1,10 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import UserViewSet
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
urlpatterns = [
path('', include(router.urls)),
]

View File

@@ -0,0 +1,23 @@
from rest_framework import viewsets, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import User
from .serializers import UserSerializer, UserDetailSerializer
class UserViewSet(viewsets.ModelViewSet):
"""ViewSet for User model."""
queryset = User.objects.all()
permission_classes = [permissions.IsAuthenticated]
def get_serializer_class(self):
if self.action in ['retrieve', 'update', 'partial_update']:
return UserDetailSerializer
return UserSerializer
@action(detail=False, methods=['get'])
def me(self, request):
"""Get current user."""
serializer = self.get_serializer(request.user)
return Response(serializer.data)