From b45daf566e1e1999065496741011e36ab172fd3a Mon Sep 17 00:00:00 2001 From: Ayush Pandey Date: Wed, 8 Jul 2026 00:33:14 +0530 Subject: [PATCH 1/2] fix: prevent duplicate project applications --- backend/app/services/application_service.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/app/services/application_service.py b/backend/app/services/application_service.py index 63a92435..9083ea54 100644 --- a/backend/app/services/application_service.py +++ b/backend/app/services/application_service.py @@ -2,6 +2,7 @@ import uuid +from fastapi import HTTPException, status from sqlalchemy import select from sqlalchemy.orm import Session @@ -28,7 +29,18 @@ def create_application( flare_id: uuid.UUID, application: ApplicationCreate, ) -> Application: + existing_application = db.scalar( + select(Application).where( + Application.applicant_id == applicant_id, + Application.project_id == project_id, + ) + ) + if existing_application: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="You have already applied to this project.", + ) db_application = Application( applicant_id=applicant_id, project_id=project_id, From cfd7c867718965810119b098525182a1371f70f4 Mon Sep 17 00:00:00 2001 From: Ayush Pandey Date: Thu, 9 Jul 2026 22:07:07 +0530 Subject: [PATCH 2/2] fix: enforce database-level duplicate application prevention --- ..._add_unique_constraint_for_applications.py | 34 +++++++++++++++++++ backend/app/models/application.py | 9 ++++- backend/app/services/application_service.py | 14 ++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 backend/alembic/versions/398a2154b3d5_add_unique_constraint_for_applications.py diff --git a/backend/alembic/versions/398a2154b3d5_add_unique_constraint_for_applications.py b/backend/alembic/versions/398a2154b3d5_add_unique_constraint_for_applications.py new file mode 100644 index 00000000..5f65299e --- /dev/null +++ b/backend/alembic/versions/398a2154b3d5_add_unique_constraint_for_applications.py @@ -0,0 +1,34 @@ +"""add unique constraint for applications + +Revision ID: 398a2154b3d5 +Revises: add_unique_constraint_applicant_project +Create Date: 2026-07-09 21:40:36.723685 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '398a2154b3d5' +down_revision: Union[str, Sequence[str], None] = "c14aa06f723a" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_unique_constraint( + "uq_applicant_project", + "applications", + ["applicant_id", "project_id"], + ) + + +def downgrade() -> None: + op.drop_constraint( + "uq_applicant_project", + "applications", + type_="unique", + ) diff --git a/backend/app/models/application.py b/backend/app/models/application.py index ef9ec136..0b6249e3 100644 --- a/backend/app/models/application.py +++ b/backend/app/models/application.py @@ -11,6 +11,7 @@ ForeignKey, String, Text, + UniqueConstraint, func, ) from sqlalchemy.dialects.postgresql import UUID @@ -33,7 +34,13 @@ class Application(Base): """ __tablename__ = "applications" - + __table_args__ = ( + UniqueConstraint( + "applicant_id", + "project_id", + name="uq_applicant_project", + ), + ) # ========================================================== # Primary Key # ========================================================== diff --git a/backend/app/services/application_service.py b/backend/app/services/application_service.py index 9083ea54..9c655dc8 100644 --- a/backend/app/services/application_service.py +++ b/backend/app/services/application_service.py @@ -4,6 +4,7 @@ from fastapi import HTTPException, status from sqlalchemy import select +from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from app.models.application import ( @@ -52,10 +53,17 @@ def create_application( ) db.add(db_application) - db.commit() - db.refresh(db_application) - return db_application + try: + db.commit() + except IntegrityError: + db.rollback() + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="You have already applied to this project.", + ) + db.refresh(db_application) + return db_application @staticmethod def get_application(