Skip to content

Dev > Main (#48) #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 30 additions & 10 deletions page/src/apps/account/api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from rest_framework import generics, permissions, status
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.views import (
TokenObtainPairView,
)

from apps.core.models import User

from .serializers import RegistrationSerializer
from .services.auth_service import AuthService


class RegistrationApi(generics.CreateAPIView):
Expand All @@ -15,13 +18,30 @@ class RegistrationApi(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
#user = self.perform_create(serializer)
user = serializer.save()
refresh = RefreshToken.for_user(user)
try:
data = AuthService.register(**serializer.validated_data)
return Response(
data,
status=status.HTTP_201_CREATED
)
except Exception as e:
return Response(
{"error": str(e)},
status=status.HTTP_400_BAD_REQUEST
)


class LoginView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
login_data = AuthService.login(
username=request.data['username'],
password=request.data['password']
)
if login_data:
return Response(login_data)
return Response(
{
"refresh": str(refresh),
"access": str(refresh.access_token),
},
status=status.HTTP_201_CREATED
)
{'error': 'Invalid credentials'},
status=status.HTTP_401_UNAUTHORIZED
)
28 changes: 28 additions & 0 deletions page/src/apps/account/repos/user_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.core.exceptions import ObjectDoesNotExist

from apps.core.models import User


class UserRepo:
@staticmethod
def create_user(
username: str, email: str, password: str
) -> User:
user = User(
username=username,
email=email,
password=password
)
user.set_password(password)
user.save()
return user

@staticmethod
def get_user_by_username(
username: str
) -> User:
try:
user = User.objects.get(username=username)
return user
except ObjectDoesNotExist:
return None
41 changes: 41 additions & 0 deletions page/src/apps/account/services/auth_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Optional

from django.contrib.auth import authenticate
from rest_framework_simplejwt.tokens import RefreshToken

from apps.core.models import User

from ..repos.user_repo import UserRepo


class AuthService:
@staticmethod
def register(
username: str, email: str, password: str, password2: str
) -> dict:
if password != password2:
return {"error": "Passwords do not match"}

user: User = UserRepo.create_user(username, email, password)
refresh = RefreshToken.for_user(user)
return {
"refresh": str(refresh),
"access": str(refresh.access_token),
}

@staticmethod
def login(
username: str, password: str
) -> dict:
user: Optional[User] = authenticate(username=username, password=password)
if not user:
return {"error": "User not found"}

if not user.check_password(password):
return {"error": "Invalid password"}

refresh = RefreshToken.for_user(user)
return {
"refresh": str(refresh),
"access": str(refresh.access_token),
}
13 changes: 11 additions & 2 deletions page/src/apps/account/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
from django.urls import path
from rest_framework_simplejwt.views import (
TokenRefreshView,
TokenVerifyView,
)

from .api import RegistrationApi
from .api import LoginView, RegistrationApi
from .views import ProtectedView

urlpatterns = [
path("api/register", RegistrationApi.as_view(), name="register"),
path("api/register/", RegistrationApi.as_view(), name="register"),
path('api/login/', LoginView.as_view(), name='login'),
path('api/login/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
path("api/protected/", ProtectedView.as_view(), name="protected"),
]
11 changes: 11 additions & 0 deletions page/src/apps/account/views.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
from rest_framework import generics, permissions
from rest_framework.response import Response


class ProtectedView(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated]

def get(self, request, *args, **kwargs):
content = {
'message': 'This is a protected view, only accessible with a valid token.'
}
return Response(content)
10 changes: 8 additions & 2 deletions page/src/apps/core/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Generated by Django 4.1.9 on 2025-04-06 16:28
# Generated by Django 4.1.9 on 2025-04-18 16:24

import uuid

import django.contrib.auth.models
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
Expand All @@ -13,9 +14,9 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
('auth', '0012_alter_user_first_name_max_length'),
('contenttypes', '0002_remove_content_type_name'),
('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
]

operations = [
Expand Down Expand Up @@ -58,7 +59,12 @@ class Migration(migrations.Migration):
('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',
'ordering': ['username'],
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0002_source.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 16:33
# Generated by Django 4.1.9 on 2025-04-18 16:48

import uuid

Expand Down
5 changes: 4 additions & 1 deletion page/src/apps/core/migrations/0003_state.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 16:35
# Generated by Django 4.1.9 on 2025-04-18 16:49

import uuid

Expand All @@ -16,6 +16,9 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='State',
fields=[
('deleted_at', models.DateTimeField(blank=True, null=True)),
('restored_at', models.DateTimeField(blank=True, null=True)),
('transaction_id', models.UUIDField(blank=True, null=True)),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('ref', models.UUIDField(editable=False)),
('state', models.CharField(max_length=32)),
Expand Down
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0004_code.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 16:39
# Generated by Django 4.1.9 on 2025-04-18 16:50

import uuid

Expand Down
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0005_language.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 16:40
# Generated by Django 4.1.9 on 2025-04-18 16:50

import uuid

Expand Down
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0006_coordinate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 16:46
# Generated by Django 4.1.9 on 2025-04-18 16:51

import uuid

Expand Down
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0007_location.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 16:55
# Generated by Django 4.1.9 on 2025-04-18 16:51

import uuid

Expand Down
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0008_address.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-06 18:32
# Generated by Django 4.1.9 on 2025-04-18 16:51

import uuid

Expand Down
6 changes: 3 additions & 3 deletions page/src/apps/core/migrations/0009_url.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-13 16:52
# Generated by Django 4.1.9 on 2025-04-18 16:52

import uuid

Expand Down Expand Up @@ -30,8 +30,8 @@ class Migration(migrations.Migration):
('language', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='urls', to='core.language')),
],
options={
'verbose_name': 'URL',
'verbose_name_plural': 'URLs',
'verbose_name': 'Url',
'verbose_name_plural': 'Urls',
'ordering': ['url'],
},
),
Expand Down
2 changes: 1 addition & 1 deletion page/src/apps/core/migrations/0010_platform.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.9 on 2025-04-13 17:31
# Generated by Django 4.1.9 on 2025-04-18 16:53

import uuid

Expand Down
14 changes: 8 additions & 6 deletions page/src/apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase


class Tag(SoftDeleteModel, GenericUUIDTaggedItemBase, TaggedItemBase):
class Tag(GenericUUIDTaggedItemBase, TaggedItemBase, SoftDeleteModel):
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")


class User(SoftDeleteModel, AbstractUser):
class User(AbstractUser, SoftDeleteModel):
# Django's AbstractUser already includes the following fields by default:
# - id (AutoField, primary key)
# - password
Expand All @@ -35,6 +35,8 @@ class User(SoftDeleteModel, AbstractUser):

class Meta:
ordering = ["username"]
verbose_name = _("User")
verbose_name_plural = _("Users")

def __str__(self) -> str:
return f"[User: {self.username} {self.email}]"
Expand All @@ -59,7 +61,7 @@ def __str__(self) -> str:
return f"[Source: {self.type}, {self.subtype}, {self.origin}, {self.source}]"


class State(models.Model):
class State(SoftDeleteModel):
id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
ref = models.UUIDField(editable=False, blank=False, null=False)
source = models.ForeignKey(
Expand Down Expand Up @@ -350,11 +352,11 @@ class Type(models.TextChoices):

class Meta:
ordering = ["url"]
verbose_name = _("URL")
verbose_name_plural = _("URLs")
verbose_name = _("Url")
verbose_name_plural = _("Urls")

def __str__(self):
return f"[URL: {self.url}]"
return f"[Url: {self.url}]"


class Platform(SoftDeleteModel):
Expand Down