-
Notifications
You must be signed in to change notification settings - Fork 1
docs(jwt): Fix endpoints table and add missing logout route #16
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
Changes from all commits
279000c
d0f6dab
d8af7ba
fe09969
46a3afa
e175a1c
dc2c90a
3560ed1
0bc12b2
2c03152
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| from django.apps import AppConfig | ||
|
|
||
|
|
||
| class PropertiesConfig(AppConfig): | ||
| default_auto_field = "django.db.models.BigAutoField" | ||
| name = "apps.properties" | ||
| label = "properties" | ||
|
|
||
| def ready(self): | ||
| import apps.properties.signals # importa o arquivo de signals | ||
| import apps.properties.signals |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 5.2 on 2026-03-30 20:19 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('users', '0002_alter_user_options_remove_user_username_user_name_and_more'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterModelManagers( | ||
| name='user', | ||
| managers=[ | ||
| ], | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,27 @@ | ||||||||||||||
| # apps/users/models.py | ||||||||||||||
| from django.contrib.auth.models import AbstractUser | ||||||||||||||
| from django.contrib.auth.models import AbstractUser, BaseUserManager | ||||||||||||||
| from django.db import models | ||||||||||||||
|
|
||||||||||||||
| class UserManager(BaseUserManager): | ||||||||||||||
| def create_user(self, email, password=None, **extra_fields): | ||||||||||||||
| if not email: | ||||||||||||||
| raise ValueError("Email is required") | ||||||||||||||
| email = self.normalize_email(email) | ||||||||||||||
| user = self.model(email=email, **extra_fields) | ||||||||||||||
| user.set_password(password) | ||||||||||||||
| user.save(using=self._db) | ||||||||||||||
| return user | ||||||||||||||
| def create_superuser(self, email, 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 need is_staff=True.") | ||||||||||||||
| if extra_fields.get("is_superuser") is not True: | ||||||||||||||
| raise ValueError("Superuser need is_superuser=True.") | ||||||||||||||
|
Comment on lines
+19
to
+21
|
||||||||||||||
| raise ValueError("Superuser need is_staff=True.") | |
| if extra_fields.get("is_superuser") is not True: | |
| raise ValueError("Superuser need is_superuser=True.") | |
| raise ValueError("Superuser needs is_staff=True.") | |
| if extra_fields.get("is_superuser") is not True: | |
| raise ValueError("Superuser needs is_superuser=True.") |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,4 +28,25 @@ def update(self, instance, validated_data): | |||||
| defaults=preferences_data | ||||||
| ) | ||||||
|
|
||||||
| return instance | ||||||
| return instance | ||||||
|
|
||||||
| class RegisterSerializer(serializers.ModelSerializer): | ||||||
| password = serializers.CharField(write_only=True, min_length=8) | ||||||
|
|
||||||
| class Meta: | ||||||
| model = User | ||||||
| fields = ['id', 'name', 'email', 'password', 'user_type'] | ||||||
|
|
||||||
| def create(self, validated_data): | ||||||
| user = User.objects.create_user( | ||||||
| email=validated_data['email'], | ||||||
| name=validated_data['name'], | ||||||
| user_type=validated_data['user_type'], | ||||||
| password=validated_data['password'] | ||||||
| ) | ||||||
| return user | ||||||
|
||||||
| return user | |
| return user |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,14 @@ | ||
| from rest_framework.routers import DefaultRouter | ||
| from .views import UserViewSet | ||
| from .views import UserViewSet, RegisterUserView | ||
| from django.urls import path | ||
| from rest_framework_simplejwt.views import (TokenObtainPairView, TokenRefreshView, TokenBlacklistView) | ||
|
|
||
| router = DefaultRouter() | ||
| router.register(r"", UserViewSet, basename="user") | ||
| urlpatterns = router.urls | ||
| urlpatterns = [ | ||
| path('register/', RegisterUserView.as_view(), name='register'), | ||
| path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'), | ||
| path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), | ||
| path('logout/', TokenBlacklistView.as_view(), name='logout') | ||
| ] + router.urls | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,18 +1,23 @@ | ||||||
| from rest_framework import viewsets, status | ||||||
| from rest_framework import viewsets, status, generics | ||||||
| from rest_framework.decorators import action | ||||||
| from rest_framework.response import Response | ||||||
| from rest_framework.permissions import IsAuthenticated | ||||||
| from rest_framework.permissions import IsAuthenticated, AllowAny | ||||||
| from django.shortcuts import get_object_or_404 | ||||||
| from .models import User | ||||||
| from .serializers import UserSerializer | ||||||
| from .serializers import UserSerializer, RegisterSerializer | ||||||
|
|
||||||
| class RegisterUserView(generics.CreateAPIView): | ||||||
| queryset = User.objects.all() | ||||||
| permission_classes = [AllowAny] | ||||||
|
||||||
| permission_classes = [AllowAny] | |
| permission_classes = [AllowAny] |
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This endpoint relies on user.favorites (used later in the method), but the User model in this PR does not define a favorites relation (the ManyToMany field is commented out and there is no alternative relation in apps/properties). This will raise an AttributeError at runtime. Either add/restore the favorites ManyToMany (with a migration) or update the endpoint to use the actual persisted favorites model/table.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| erDiagram | ||
| USERS { | ||
| bigserial id PK | ||
| text name | ||
| int age | ||
| varchar gender | ||
| text email UK "Login principal" | ||
| varchar user_type "A, S" | ||
| timestamp created_at | ||
| } | ||
|
|
||
| SEARCH_PREFERENCES { | ||
| bigserial id PK | ||
| bigint user_id FK | ||
| char property_type "H, A" | ||
| numeric min_price | ||
| numeric max_price | ||
| varchar city | ||
| varchar neighborhood | ||
| } | ||
|
|
||
| USER_FAVORITES { | ||
| bigint user_id FK | ||
| bigint property_id FK | ||
| } | ||
|
|
||
| PROPERTIES { | ||
| bigserial id PK | ||
| bigint owner_id FK | ||
| bigint rooms_id FK | ||
| bigint rooms_extras_id FK | ||
| bigint condo_id FK | ||
| char property_purpose "R, S, B" | ||
| char type "H, A" | ||
| float area | ||
| int floors | ||
| int floor_number | ||
| numeric price | ||
| text address | ||
| varchar neighborhood | ||
| varchar city | ||
| boolean status | ||
| boolean has_mobilia | ||
| text description | ||
| vector embedding "1536 dim" | ||
| timestamp created_at | ||
| } | ||
|
|
||
| ROOMS { | ||
| bigserial id PK | ||
| int bedrooms | ||
| int bathrooms | ||
| int parking_spots | ||
| } | ||
|
|
||
| ROOMS_EXTRAS { | ||
| bigserial id PK | ||
| boolean living_room | ||
| boolean garden | ||
| boolean kitchen | ||
| boolean laundry_room | ||
| boolean pool | ||
| boolean office | ||
| } | ||
|
|
||
| CONDO { | ||
| bigserial id PK | ||
| text name | ||
| text address | ||
| boolean gym | ||
| boolean pool | ||
| boolean court | ||
| boolean parks | ||
| boolean party_spaces | ||
| boolean concierge | ||
| boolean laundry_room | ||
| } | ||
|
|
||
| PROPERTIES_PHOTOS { | ||
| bigserial id PK | ||
| bigint property_id FK | ||
| text r2_key "Cloudflare R2" | ||
| int order | ||
| } | ||
|
|
||
| USERS ||--o{ PROPERTIES : "owns" | ||
| USERS ||--o| SEARCH_PREFERENCES : "has" | ||
| USERS }o--o{ USER_FAVORITES : "saves" | ||
| PROPERTIES }o--o{ USER_FAVORITES : "saved_by" | ||
| PROPERTIES ||--|| ROOMS : "has" | ||
| PROPERTIES ||--|| ROOMS_EXTRAS : "features" | ||
| PROPERTIES }o--|| CONDO : "belongs_to" | ||
| PROPERTIES ||--o{ PROPERTIES_PHOTOS : "has_images" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||
| # Authentication & Security Documentation | ||||||
|
|
||||||
| Esta documentação detalha a implementação do sistema de segurança e autenticação do projeto **HomeMatch**. | ||||||
|
|
||||||
| ## Visão Geral | ||||||
| A plataforma utiliza **JSON Web Tokens (JWT)** via `djangorestframework-simplejwt` para gerenciar sessões e permissões. O diferencial do nosso modelo é a autenticação baseada exclusivamente em **E-mail**, tendo o campo `username` sido removido para simplificar o fluxo do usuário. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Modelo de Usuário Customizado | ||||||
| O modelo `User` herda de `AbstractUser`, mas utiliza um `UserManager` customizado para suportar o e-mail como identificador único. | ||||||
|
|
||||||
| * **Identificador de Login**: `email`. | ||||||
| * **Campos Obrigatórios**: `email`, `name`. | ||||||
| * **Tipos de Usuário (user_type)**: | ||||||
| * `A` (Advertiser/Anunciante). | ||||||
| * `S` (Seeker/Buscador). | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Endpoints de Autenticação (JWT) | ||||||
|
|
||||||
| | Método | Endpoint | Acesso | Descrição | | ||||||
| | :--- | :--- | :--- | :--- | | ||||||
| | `POST` | `/api/users/register/` | Público | Registra um novo usuário na plataforma. | | ||||||
| | `POST` | `/api/users/login/` | Público | Recebe as credenciais e retorna os tokens `access` e `refresh`. | | ||||||
| | `POST` | `/api/users/token/refresh/` | Público | Gera um novo `access` token utilizando um `refresh` válido. | | ||||||
|
Comment on lines
+23
to
+27
|
||||||
| | `POST` | `/api/users/logout/` | Autenticado | Invalida o `refresh` token (blacklist), encerrando a sessão. | | ||||||
| | `GET` | `/api/users/me/` | Autenticado | Retorna os dados do perfil do usuário logado. | | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Proteção de Rotas: Imóveis (Properties) | ||||||
|
|
||||||
| As rotas do app `properties` seguem a política de permissão `IsAuthenticatedOrReadOnly`. | ||||||
|
|
||||||
| ### 1. Leitura de Dados (Público) | ||||||
| Qualquer usuário pode realizar as seguintes operações sem necessidade de token: | ||||||
| * `GET /api/properties/`: Lista todos os imóveis cadastrados. | ||||||
| * `GET /api/properties/{id}/`: Visualiza detalhes de um imóvel específico. | ||||||
|
|
||||||
| ### 2. Escrita de Dados (Autenticado) | ||||||
| Requer o envio do cabeçalho `Authorization: Bearer <access_token>`. | ||||||
|
|
||||||
| * **Criação (`POST /api/properties/`)**: Permitida apenas para usuários com `user_type = 'A'` (Advertiser). | ||||||
| * **Edição (`PATCH /api/properties/{id}/`)**: Restrita ao proprietário do imóvel (`owner_id`). | ||||||
| * **Remoção (`DELETE /api/properties/{id}/`)**: Restrita ao proprietário do imóvel (`owner_id`). | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Tratamento de Erros | ||||||
|
|
||||||
| O sistema utiliza códigos de erro padronizados do Django REST Framework: | ||||||
|
|
||||||
| * **401 Unauthorized**: Token ausente, expirado ou inválido. | ||||||
| * **403 Forbidden**: Usuário autenticado tenta realizar uma ação sem permissão (ex: Seeker tentando postar imóvel ou editar imóvel de terceiros). | ||||||
| * **400 Bad Request**: Erros de validação (ex: e-mail já cadastrado ou senha fora do padrão). | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Exemplo de Requisição (CURL) | ||||||
|
|
||||||
| Para acessar rotas protegidas no terminal, utilize: | ||||||
|
|
||||||
| ```bash | ||||||
| docker compose exec web python manage.py createsuperuser # Pra criar seu usuario - Se ainda não criado | ||||||
|
||||||
| docker compose exec web python manage.py createsuperuser # Pra criar seu usuario - Se ainda não criado | |
| docker compose exec web python manage.py createsuperuser # Para criar seu usuário (caso ainda não tenha sido criado) |
Copilot
AI
Apr 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence has a spelling/grammar issue ("Só ai"). Please correct to "Só aí" for proper Portuguese.
| * Só ai, você testa com seu token. | |
| * Só aí, você testa com seu token. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This migration alters model managers but provides an empty
managers=[], which is unusual and can create confusing migration state (and it doesn't affect the DB schema). Consider removing it, or include the intended manager so the migration accurately represents the model state.