diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py index 667740a..622f48c 100644 --- a/core/apps/accounts/urls.py +++ b/core/apps/accounts/urls.py @@ -4,24 +4,16 @@ Accounts app urls from django.urls import path, include from rest_framework_simplejwt import views as jwt_views -from .views import ( - RegisterView, - ResetPasswordView, - MeView, - ChangePasswordView, - MeCompanyView, -) +from . import views from rest_framework.routers import DefaultRouter # type: ignore router = DefaultRouter() -router.register("auth", RegisterView, basename="auth") # type: ignore -router.register("auth", ResetPasswordView, basename="reset-password") # type: ignore -router.register("auth", MeView, basename="me") # type: ignore -router.register("auth", ChangePasswordView, basename="change-password") # type: ignore - -router.register(r"me/companies", MeCompanyView, "me-company") # type: ignore +router.register("auth", views.RegisterView, basename="auth") # type: ignore +router.register("auth", views.ResetPasswordView, basename="reset-password") # type: ignore +router.register("auth", views.MeView, basename="me") # type: ignore +router.register("auth", views.ChangePasswordView, basename="change-password") # type: ignore urlpatterns = [ # type: ignore path("", include(router.urls)), # type: ignore @@ -32,4 +24,14 @@ urlpatterns = [ # type: ignore jwt_views.TokenRefreshView.as_view(), name="token_refresh", ), + path( + r"users//companies", + views.UserCompanyApiView.as_view(), + name="user-company-api-view" + ), + path( + r"me/companies", + views.MeCompanyApiView.as_view(), + name="me-company-api-view" + ) ] diff --git a/core/apps/accounts/views/me.py b/core/apps/accounts/views/me.py index 77c2bac..56cf2a8 100644 --- a/core/apps/accounts/views/me.py +++ b/core/apps/accounts/views/me.py @@ -1,4 +1,4 @@ -from rest_framework.viewsets import GenericViewSet # type: ignore +from rest_framework.generics import GenericAPIView # type: ignore from rest_framework.decorators import action # type: ignore from rest_framework import status # type: ignore from rest_framework.request import HttpRequest # type: ignore @@ -7,8 +7,7 @@ from rest_framework.permissions import ( # type: ignore IsAuthenticated ) -from django_core.mixins import BaseViewSetMixin # type: ignore - +from core.utils.views import BaseApiViewMixin from core.apps.companies.serializers import ( RetrieveCompanySerializer, CreateCompanySerializer, @@ -21,50 +20,29 @@ from core.apps.companies.models import ( from django.db import transaction -class MeCompanyView(BaseViewSetMixin, GenericViewSet): +###################################################################### +# @api-view | POST, GET - me/companies +###################################################################### +class MeCompanyApiView(BaseApiViewMixin, GenericAPIView): # type: ignore permission_classes = [IsAuthenticated] - action_permission_classes = {} - action_serializer_class = { - "create": CreateCompanySerializer, - "list": RetrieveCompanySerializer, + method_permission_classes = {} + method_serializer_class = { + "post": CreateCompanySerializer, + "get": RetrieveCompanySerializer, } - def list( - self, - request: HttpRequest, - *args: object, - **kwargs: object - ) -> Response: - - companies = CompanyModel.objects.filter( - accounts__user=request.user - ) - - return Response( - RetrieveCompanySerializer(instance=companies, many=True).data, - status=status.HTTP_200_OK - ) + def get(self, request: HttpRequest, *args: object, **kwargs: object) -> Response: + companies = CompanyModel.objects.filter(accounts__user=request.user) + ser = RetrieveCompanySerializer(instance=companies, many=True) + return Response(ser.data, status.HTTP_200_OK) - def create( - self, - request: HttpRequest, - *args: object, - **kwargs: object - ) -> Response: - + def create(self, request: HttpRequest, *args: object, **kwargs: object) -> Response: with transaction.atomic(): serializer = CreateCompanySerializer(data=request.data) # type: ignore serializer.is_valid(raise_exception=True) company = serializer.save() # type: ignore - account = CompanyAccountModel( - company=company, - user=request.user - ) + account = CompanyAccountModel(company=company, user=request.user) account.save() - - return Response( - data=serializer.data, - status=status.HTTP_201_CREATED - ) \ No newline at end of file + return Response(serializer.data, status.HTTP_201_CREATED) \ No newline at end of file diff --git a/core/apps/accounts/views/users.py b/core/apps/accounts/views/users.py index d7ed7b2..3530cdf 100644 --- a/core/apps/accounts/views/users.py +++ b/core/apps/accounts/views/users.py @@ -2,24 +2,25 @@ import uuid from drf_spectacular.utils import extend_schema -from rest_framework.viewsets import GenericViewSet # type: ignore +from rest_framework.generics import GenericAPIView # type: ignore from rest_framework.decorators import action # type: ignore +from rest_framework.permissions import IsAdminUser from rest_framework import status # type: ignore from rest_framework.request import HttpRequest # type: ignore from rest_framework.response import Response # type: ignore from rest_framework.permissions import ( # type: ignore IsAdminUser, ) -from django_core.mixins import BaseViewSetMixin from rest_framework.generics import get_object_or_404 # type: ignore from django.contrib.auth import get_user_model from django.db import transaction - +from core.utils.views import BaseApiViewMixin from core.apps.companies.serializers import ( CreateCompanySerializer, - RetrieveCompanySerializer + RetrieveCompanySerializer, + BaseCompanySerializer, ) from core.apps.companies.models import ( CompanyModel, @@ -29,47 +30,53 @@ from core.apps.companies.models import ( UserModel = get_user_model() -class UserCompaniesView(BaseViewSetMixin, GenericViewSet): +###################################################################### +# /users/{id}/companies +###################################################################### +@extend_schema(tags=["User Companies"]) +class UserCompanyApiView(BaseApiViewMixin, GenericAPIView): # type: ignore + queryset = UserModel.objects.all() permission_classes = [IsAdminUser] + serializer_class = BaseCompanySerializer - action_permission_classes = {} - action_permission_classes = { - "list_company": RetrieveCompanySerializer, - "create_company": CreateCompanySerializer, + method_permission_classes = { + "get": [IsAdminUser], + "post": [IsAdminUser], + } + method_serializer_class = { + "get": RetrieveCompanySerializer, + "post": CreateCompanySerializer, } @extend_schema( - summary="Get list of companies", - description="Get list of companies", + summary="Get List Of Companies For User", + description="Get List Of Companies For User", ) - @action(url_path="companies", detail=True, methods=["GET"]) - def list_company( + def get( self, request: HttpRequest, pk: uuid.UUID, *args: object, **kwargs: object, ) -> Response: - companies = CompanyModel.objects.filter(accounts__user__pk=pk) return Response( data=RetrieveCompanySerializer(instance=companies, many=True), status=status.HTTP_200_OK ) - + + @extend_schema( - summary="Create Company", - description="Create Company", + summary="Create Company For User", + description="Create Company For User", ) - @action(url_path="companies", detail=True, methods=["POST"]) - def create_company( + def post( self, request: HttpRequest, pk: uuid.UUID, *args: object, **kwargs: object, ) -> Response: - with transaction.atomic(): ser = CreateCompanySerializer(data=request.data) # type: ignore ser.is_valid(raise_exception=True) @@ -81,4 +88,3 @@ class UserCompaniesView(BaseViewSetMixin, GenericViewSet): account.save() return Response(data=ser.data, status=status.HTTP_201_CREATED) - \ No newline at end of file diff --git a/core/apps/companies/views/folders.py b/core/apps/companies/views/folders.py index 55aafd4..1febd15 100644 --- a/core/apps/companies/views/folders.py +++ b/core/apps/companies/views/folders.py @@ -51,6 +51,7 @@ class CompanyFolderCrudViewSet(BaseViewSetMixin, ModelViewSet): ###################################################################### # /company-folders//contracts ###################################################################### +@extend_schema(tags=["CompanyFolder Contracts"]) class ContractFolderApiView(BaseApiViewMixin, GenericAPIView): # type: ignore queryset = CompanyFolderModel.objects.all() permission_classes = [IsFolderOwner] diff --git a/docs/COMMENTS.md b/docs/COMMENTS.md new file mode 100644 index 0000000..68174a6 --- /dev/null +++ b/docs/COMMENTS.md @@ -0,0 +1,96 @@ +## 🚀 View Commenting Convention for Fast Debugging & Navigation + +To streamline debugging and code navigation, every view class should be preceded by a **clearly structured comment block**. This format makes it easy to: + +* Quickly locate views by endpoint path or method +* Search views by type (`APIView`, `ViewSet`) via CLI tools like `rg` or `grep` +* Immediately understand what a view is responsible for + +--- + +### ✅ Format + +```python +################################################################################### +# @ | - +################################################################################### +``` + +#### Example: + +```python +################################################################################### +# @api-view | POST, GET - /me/companies +################################################################################### +class MeCompanyApiView(GenericAPIView): + ... +``` + +--- + +### 🔎 Field Reference + +| Field | Purpose | Example | +| ----------------- | ------------------------------------------------------------------- | ------------------------------ | +| `@` | Declares the kind of view used — aids CLI-based filtering/searching | `@api-view`, `@view-set` | +| `` | HTTP methods the view handles | `GET`, `POST`, `PUT`, etc. | +| `` | The URL route where the view is mounted | `/users/files//upload` | + +--- + +### 📘 View Types + +Use one of the following view-type identifiers: + +| Type | Description | +| ----------- | ------------------------------------------------------------------ | +| `@api-view` | For views that inherit from `GenericAPIView` or `APIView` | +| `@view-set` | For views that inherit from `GenericViewSet`, `ModelViewSet`, etc. | + +--- + +### ♻️ HTTP Methods + +List only the HTTP methods the view explicitly supports: + +* `GET` +* `POST` +* `PUT` +* `PATCH` +* `DELETE` +* `OPTIONS` + +Order doesn't matter, but use **uppercase** for consistency. + +--- + +### 📍 Endpoint Path + +This should reflect the actual route where the view is registered. + +Use Django/DRF-style parameters: + +```text +/users//folders/ +/files//download/ +/me/companies/ +``` + +--- + +### 🚧 Best Practices + +* Use this comment format **before every `GenericAPIView`, `ViewSet`, or similar**. +* Avoid including permissions, descriptions, or extra metadata unless necessary. +* Keep comments strictly scoped to navigation/debugging. +* Use CLI tools to search: + + ```bash + rg "@api-view" + rg "companies" + rg "POST, GET" + ``` + +--- + +This pattern ensures developers can **lightning-fast locate views**, trace bugs, and maintain high code readability in large DRF codebases. diff --git a/docs/ENDPOINTS.md b/docs/ENDPOINTS.md new file mode 100644 index 0000000..3e24f6d --- /dev/null +++ b/docs/ENDPOINTS.md @@ -0,0 +1,175 @@ +## 📌 API Endpoint Overview + +This document provides a categorized and role-specific overview of all API endpoints implemented (or to be implemented) in the system. It is structured for easy readability, regular updates, and fast navigation. + +--- + +### ⚖️ Roles + +* `admin` — Endpoints accessible only to admins. +* `user` — Endpoints accessible to regular authenticated users. + +> Status Legend: +> +> * `ok` — Fully implemented and tested +> * `partial` — Implemented but incomplete or unstable +> * `TODO` — Needs implementation +> * `not ok` — Implemented but incorrect or buggy + +--- + +## 🔑 Auth & Users + +#### users: + +* `POST /auth/register` — user — remake +* `POST /auth/verify` — user — ok +* `GET /auth/me` — user — ok + +#### me/companies: + +* `GET /me/companies` — user — ok +* `POST /me/companies` — user — ok + +#### users/: + +* `GET /users//companies` — user — ok +* `POST /users//companies` — user — ok + +--- + +## 🏢 Companies + +#### companies: + +* `GET /companies` — admin — ok + +* `POST /companies` — admin — ok + +* `GET /companies/` — admin — ok + +* `DELETE /companies/` — admin — ok + +* `PATCH /companies/` — admin — ok + +* `GET /companies//contracts` — user — partial + + * filters: `folder`, `status: list[str]` + +* `GET /companies//folders` — user — ok + +* `POST /companies//folders` — user — ok + +* `GET /companies//accounts` — user — ok + +* `POST /companies//accounts` — user — TODO + +--- + +## 💳 Company Accounts + +* `GET /company-accounts` — admin — ok + +* `POST /company-accounts` — admin — ok + +* `GET /company-accounts/` — admin — ok + +* `PATCH /company-accounts/` — admin — ok + +* `DELETE /company-accounts/` — admin — ok + +* `POST /accounts/verify` — user — TODO + + * required: `phone`, `code` + +--- + +## 🌐 Banks + +* `GET /banks` — admin — ok +* `POST /banks` — admin — ok +* `GET /banks/` — admin — ok +* `DELETE /banks/` — admin — ok +* `PATCH /banks/` — admin — ok + +--- + +## 📍 Contracts + +* `GET /contracts` — admin — ok + +* `POST /contracts` — user — ok + +* `GET /contracts/` — admin — ok + +* `DELETE /contracts/` — admin — ok + +* `PATCH /contracts/` — admin — ok + +* `GET /contracts//files` — user — ok + +* `GET /contracts//owners` — user — ok + +--- + +## 📄 Contract Owners + +* `GET /contract-owners` — admin — ok + +* `POST /contract-owners` — admin — ok + +* `GET /contract-owners/` — admin — ok + +* `DELETE /contract-owners/` — admin — ok + +* `PATCH /contract-owners/` — admin — ok + +* `GET /contract-owners//contract` — user — ok + +* `POST /contract-owners//files` — user — not ok + +* `GET /contract-owners//files` — user — not ok + +* `DELETE /contract-owners//files/` — user — ok + +* `POST /contract-owners//files//upload` — user — ok + +--- + +## 📂 Files + +* `GET /files` — admin — ok +* `POST /files` — admin — ok +* `GET /files/` — admin — ok +* `DELETE /files/` — admin — ok +* `PATCH /files/` — admin — ok + +--- + +## 📁 Folders + +* `GET /folders` — admin — ok + +* `POST /folders` — admin — ok + +* `GET /folders/` — admin — ok + +* `DELETE /folders/` — admin — ok + +* `PATCH /folders/` — admin — ok + +* `GET /folders//contracts` — admin — ok + +--- + +## 📋 File Contents + +* `GET /file-contents` — admin — ok +* `POST /file-contents` — admin — ok +* `GET /file-contents/` — admin — ok +* `DELETE /file-contents/` — admin — ok +* `PATCH /file-contents/` — admin — ok + +--- + +This structure ensures developers can **navigate quickly**, **see responsibilities by domain**, and easily **track implementation status**. Update it regularly as your API evolves.