Gabriel Rocha
Projects

STELLA

Web platform for tracking IVF treatment for patients at Clínica AMARE, with schedule, medications, and a diary.

2026RepositoryLive demo

STELLA is a web application for tracking In Vitro Fertilization (IVF) treatment, built as a team (Group 10 at CESAR School) for patients at Clínica AMARE. The platform centralizes consultations, exams, medications, symptoms, and educational materials from the journey, giving the patient back control over her own treatment. On this page I detail the Django API, where most of the system logic lives, and give a quick overview of the front-end.

Architecture

The monorepo has backend/, frontend/, and e2e/:

Models

The tratamento Django app concentrates the models:

A signal ensures every new user is created with a profile:

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

Authentication

Login is its own endpoint (/api/auth/login/) instead of the default Django REST view, because login is by email. It finds the user by email (case-insensitive), authenticates with the password, and returns an authtoken token:

user = User.objects.filter(email__iexact=email).first()
if user is None:
    return Response({'detail': 'E-mail ou senha inválidos.'},
                    status=status.HTTP_401_UNAUTHORIZED)

authenticated_user = authenticate(request, username=user.username, password=password)
if authenticated_user is None:
    return Response({'detail': 'E-mail ou senha inválidos.'},
                    status=status.HTTP_401_UNAUTHORIZED)

token, _ = Token.objects.get_or_create(user=authenticated_user)
return Response({'token': token.key, 'user': { ... }})

Protected views use TokenAuthentication + IsAuthenticated, and the front end sends the token in the header.

The API views

The profile (UserProfileRetrieveUpdateAPIView) returns the user with the nested profile and, on PATCH/PUT, updates both: first the User, then the UserProfile created on demand with get_or_create. The photo has a separate endpoint that requires the file in request.FILES.

The diary is the only resource that is clearly per user: the view filters by the logged-in user’s check-ins and saves with paciente=self.request.user:

class DiarioListCreate(generics.ListCreateAPIView):
    serializer_class = DiarioSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return Diario.objects.filter(paciente=self.request.user)

    def perform_create(self, serializer):
        serializer.save(paciente=self.request.user)

The schedule, phases, and medications, on the other hand, use queryset = Model.objects.all() with no authentication and no owner, as a shared global set:

class EventoAgendaListCreate(generics.ListCreateAPIView):
    queryset = EventoAgenda.objects.all()
    serializer_class = EventoAgendaSerializer

The co-pilot invite returns the token in the response so React can assemble the link (/convite/<token>), and ConviteCopilotoSerializer validates the nome_identificador.

Configuration

settings.py follows the 12-factor pattern: SECRET_KEY, DEBUG, ALLOWED_HOSTS, and CORS come from environment variables with development fallbacks. The database, however, has the engine fixed on SQLite, with only the path changed via DB_PATH:

_db_path = os.environ.get('DB_PATH', str(BASE_DIR / 'db.sqlite3'))
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': _db_path,
    }
}

CSRF_TRUSTED_ORIGINS point to the backend and front-end domains on Azure.

Tests, CI/CD, and deploy

There are 32 API tests (Django APITestCase) and 23 E2E tests with Selenium. GitHub Actions runs the suite on every push to main and automates the deploy: the front end goes to Azure Static Web Apps and the backend to Azure App Service.

Particularities and points I would revisit

  1. Inconsistent scope and authorization between resources. The diary is filtered by the authenticated user, but FaseTratamento, EventoAgenda, and Medicacao expose objects.all() with no authentication and no owner. For an individual platform, that data should be per patient; today any request (even without login) lists everything. The contrast between the two approaches is clear in the code and is what I would tackle first.

  2. Co-pilot invite with a “mock” user. ConviteCopilotoCreate ignores who is logged in and saves the invite for User.objects.first():

    def perform_create(self, serializer):
        usuario_teste = User.objects.first()   # Mock user
        serializer.save(paciente=usuario_teste)

    It is a development shortcut that stayed in the code: in production the invite would not belong to the right patient. On top of that, the co-pilot permissions (perm_ver_*) live in the model, but the API has no read endpoints that apply them; the link and the permissions exist, only the fine-grained control per permission does not.

  3. SQLite in “production” too. The README mentions an Azure database, but the engine is SQLite in any environment, only the file path changes. On a host that recreates the disk on redeploy, the file can be lost; a managed Postgres would avoid that risk.

  4. Secrets with a risky fallback. SECRET_KEY has a fixed fallback value and DEBUG is True by default when the env var is missing. In production the variables are set, but deploying the app without them leaves the server with debug on and a known key.

  5. Permanent token with no expiration. Login uses Token.objects.get_or_create, so the same token is valid forever, with no expiration or rotation, and there is no logout endpoint that invalidates it (only deleting it by hand). For an MVP it passes, but a JWT with expiration would be more appropriate.

  6. Media served by Django itself. The route re_path(r'^media/...', serve) serves uploads (profile photos) directly through Django; in production it is common to delegate that to external storage. Photo validation also checks only the extension, not the content or the size.

  7. Orphan dependencies in requirements.txt. django-ninja and pydantic are listed, but the code uses Django REST Framework; they probably left over from an earlier attempt.

Improvement points