feat: fcm apis

feat: push notification api
This commit is contained in:
deepvasoya 2025-05-14 17:13:11 +05:30
parent 287b6e5761
commit 2efc09cf20
11 changed files with 243 additions and 2 deletions

View File

@ -5,7 +5,7 @@ from fastapi.security import HTTPBearer
# Import the security scheme
bearer_scheme = HTTPBearer(scheme_name="Bearer Authentication")
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users, clinicDoctor, dashboard, call_transcripts
from .endpoints import clinics, doctors, calender, appointments, patients, admin, auth, s3, users, clinicDoctor, dashboard, call_transcripts, notifications
api_router = APIRouter()
# api_router.include_router(twilio.router, prefix="/twilio")
@ -37,3 +37,5 @@ api_router.include_router(clinicDoctor.router, prefix="/clinic-doctors", tags=["
api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboard"], dependencies=[Depends(auth_required)])
api_router.include_router(call_transcripts.router, prefix="/call-transcripts", tags=["call-transcripts"], dependencies=[Depends(auth_required)])
api_router.include_router(notifications.router, prefix="/notifications", tags=["notifications"], dependencies=[Depends(auth_required)])

View File

@ -0,0 +1,37 @@
from fastapi import APIRouter
from utils.constants import DEFAULT_LIMIT, DEFAULT_PAGE
from services.notificationServices import NotificationServices
from schemas.ApiResponse import ApiResponse
from fastapi import Request
router = APIRouter()
@router.get("/")
def get_notifications(request: Request, limit: int = DEFAULT_LIMIT, page: int = DEFAULT_PAGE):
if page <0:
page = 1
offset = (page - 1) * limit
notifications = NotificationServices().getNotifications(request.state.user["id"], limit, offset)
return ApiResponse(data=notifications, message="Notifications retrieved successfully")
@router.delete("/")
def delete_notification(notification_id: int):
NotificationServices().deleteNotification(notification_id)
return ApiResponse(data="OK", message="Notification deleted successfully")
@router.put("/")
def update_notification_status(notification_id: int):
NotificationServices().updateNotificationStatus(notification_id)
return ApiResponse(data="OK", message="Notification status updated successfully")
@router.post("/")
def send_notification(title: str, message: str, sender_id: int, receiver_id: int):
NotificationServices().createNotification(title, message, sender_id, receiver_id)
return ApiResponse(data="OK", message="Notification sent successfully")
@router.post("/fcm")
def send_fcm_notification(req: Request, token: str):
NotificationServices().createOrUpdateFCMToken(req.state.user["id"], token)
return ApiResponse(data="OK", message="FCM Notification sent successfully")

View File

@ -0,0 +1,51 @@
"""notification table
Revision ID: ac71b9a4b040
Revises: 0ce7107c1910
Create Date: 2025-05-14 16:14:23.750891
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ac71b9a4b040'
down_revision: Union[str, None] = '0ce7107c1910'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('notifications',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(), nullable=True),
sa.Column('message', sa.String(), nullable=True),
sa.Column('is_read', sa.Boolean(), nullable=True),
sa.Column('sender_id', sa.Integer(), nullable=False),
sa.Column('receiver_id', sa.Integer(), nullable=False),
sa.Column('create_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('update_time', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['receiver_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['sender_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_notifications_id'), 'notifications', ['id'], unique=False)
op.create_index(op.f('ix_notifications_receiver_id'), 'notifications', ['receiver_id'], unique=False)
op.create_index(op.f('ix_notifications_sender_id'), 'notifications', ['sender_id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_notifications_sender_id'), table_name='notifications')
op.drop_index(op.f('ix_notifications_receiver_id'), table_name='notifications')
op.drop_index(op.f('ix_notifications_id'), table_name='notifications')
op.drop_table('notifications')
# ### end Alembic commands ###

15
models/Fcm.py Normal file
View File

@ -0,0 +1,15 @@
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy import ForeignKey
from database import Base
from .CustomBase import CustomBase
class Fcm(Base, CustomBase):
__tablename__ = "fcm"
id = Column(Integer, primary_key=True, index=True)
token = Column(String)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
user = relationship("Users", back_populates="fcm")

19
models/Notifications.py Normal file
View File

@ -0,0 +1,19 @@
from sqlalchemy import Boolean, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
from .CustomBase import CustomBase
class Notifications(Base, CustomBase):
__tablename__ = "notifications"
id = Column(Integer, primary_key=True, index=True)
title = Column(String)
message = Column(String)
is_read = Column(Boolean, default=False)
sender_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
sender = relationship("Users", foreign_keys=[sender_id], back_populates="sent_notifications")
receiver_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
receiver = relationship("Users", foreign_keys=[receiver_id], back_populates="received_notifications")

View File

@ -3,6 +3,7 @@ from database import Base
from sqlalchemy import Enum
from enums.enums import ClinicUserRoles, UserType
from models.CustomBase import CustomBase
from sqlalchemy.orm import relationship
class Users(Base, CustomBase):
__tablename__ = "users"
@ -13,3 +14,10 @@ class Users(Base, CustomBase):
clinicRole = Column(Enum(ClinicUserRoles), nullable=True)
userType = Column(Enum(UserType), nullable=True)
profile_pic = Column(String, nullable=True)
# Notification relationships
sent_notifications = relationship("Notifications", foreign_keys="Notifications.sender_id", back_populates="sender")
received_notifications = relationship("Notifications", foreign_keys="Notifications.receiver_id", back_populates="receiver")
# FCM relationships
fcm = relationship("Fcm", back_populates="user")

View File

@ -7,6 +7,9 @@ from .Calendar import Calenders
from .AppointmentRelations import AppointmentRelations
from .MasterAppointmentTypes import MasterAppointmentTypes
from .ClinicDoctors import ClinicDoctors
from .Notifications import Notifications
from .CallTranscripts import CallTranscripts
from .Fcm import Fcm
__all__ = [
"Users",
@ -18,4 +21,7 @@ __all__ = [
"AppointmentRelations",
"MasterAppointmentTypes",
"ClinicDoctors",
"Notifications",
"CallTranscripts",
"Fcm",
]

View File

@ -85,3 +85,11 @@ class CallTranscriptsBase(BaseModel):
call_duration:str
call_received_time:str
transcript_key_id:str
class NotificationBase(BaseModel):
title: str
message: str
is_read: bool
sender_id: int
receiver_id: int

View File

@ -45,3 +45,7 @@ class ClinicDoctorCreate(ClinicDoctorBase):
class CallTranscriptsCreate(CallTranscriptsBase):
pass
class NotificationCreate(NotificationBase):
pass

View File

@ -152,3 +152,12 @@ class CallTranscriptsResponse(CallTranscriptsBase):
class Config:
orm_mode = True
class NotificationResponse(NotificationBase):
id: int
create_time: datetime
update_time: datetime
class Config:
orm_mode = True

View File

@ -0,0 +1,82 @@
from logging import Logger
from database import get_db
from sqlalchemy.orm import Session
from firebase_admin import messaging
from models import Notifications, Users, Fcm
from interface.common_response import CommonResponse
from schemas.ResponseSchemas import NotificationResponse
from exceptions.resource_not_found_exception import ResourceNotFoundException
class NotificationServices:
def __init__(self):
self.db:Session = next(get_db())
def createNotification(self, title, message, sender_id, receiver_id):
# validate sender and receiver
sender = self.db.query(Users).filter(Users.id == sender_id).first()
receiver = self.db.query(Users).filter(Users.id == receiver_id).first()
if not sender or not receiver:
raise ResourceNotFoundException("Sender or receiver not found")
notification = Notifications(title=title, message=message, sender_id=sender_id, receiver_id=receiver_id)
self.db.add(notification)
self.db.commit()
return True
def getNotifications(self, user_id, limit: int, offset: int):
notifications = self.db.query(Notifications).filter(Notifications.receiver_id == user_id).limit(limit).offset(offset).all()
total = self.db.query(Notifications).filter(Notifications.receiver_id == user_id).count()
response = CommonResponse(data=[NotificationResponse(**notification.__dict__.copy()) for notification in notifications], total=total)
return response
def deleteNotification(self, notification_id):
notification = self.db.query(Notifications).filter(Notifications.id == notification_id).first()
if not notification:
raise ResourceNotFoundException("Notification not found")
self.db.delete(notification)
self.db.commit()
return True
def updateNotificationStatus(self, notification_id):
notification = self.db.query(Notifications).filter(Notifications.id == notification_id).first()
if not notification:
raise ResourceNotFoundException("Notification not found")
notification.is_read = not notification.is_read
self.db.commit()
return True
def createOrUpdateFCMToken(self,user_id,token):
fcm = self.db.query(Fcm).filter(Fcm.token == token).first()
if fcm is None:
fcm = Fcm(user_id=user_id, token=token)
self.db.add(fcm)
self.db.commit()
return True
fcm.token = token
self.db.commit()
return True
def sendPushNotification(self, token:str, payload):
try:
message = messaging.Message(
notification=messaging.Notification(
title=payload["title"],
body=payload["body"],
),
data={},
token=token,
)
response = messaging.send(message)
return response
except:
Logger.error("Failed to send push notification")