android_app / app.py
dhvanit2026's picture
Rename main.py to app.py
07b2c16 verified
Raw
History Blame Contribute Delete
36.3 kB
import pymssql
import random
import uuid
import requests
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel
from datetime import datetime, timedelta
from typing import Optional
import os
import firebase_admin
from firebase_admin import credentials, messaging
import json
# 🔥 Firebase Setup
firebase_key = json.loads(os.environ["FIREBASE_KEY"])
cred = credentials.Certificate(firebase_key)
firebase_admin.initialize_app(cred)
# =====================================================
# CONFIG
# =====================================================
# --- Brevo Email Config ---
BREVO_API_KEY = os.getenv("KEY")
BREVO_SENDER_EMAIL = "patelkanostudent@gmail.com"
BREVO_SENDER_NAME = "College ERP System"
BREVO_API_URL = "https://api.brevo.com/v3/smtp/email"
# --- Database Config ---
DB_SERVER = "kano2026.mssql.somee.com:1433" # ✅ fixed
DB_USER = "Dhvanit_SQLLogin_1"
DB_PASSWORD = os.getenv("PASS")
DB_NAME = "kano2026"
# =====================================================
# =====================================================
# FASTAPI APP
# =====================================================
app = FastAPI(title="College ERP Backend")
# =====================================================
# DATABASE CONNECTION
# =====================================================
def get_connection():
return pymssql.connect(
server=DB_SERVER,
user=DB_USER,
password=DB_PASSWORD,
database=DB_NAME
)
# =====================================================
# BREVO EMAIL — CORE FUNCTION
# =====================================================
def send_email_brevo(to_email: str, subject: str, body: str) -> bool:
"""
Send email using Brevo transactional API.
Returns True on success, False on failure.
"""
headers = {
"accept": "application/json",
"content-type": "application/json",
"api-key": BREVO_API_KEY
}
payload = {
"sender": {
"name": BREVO_SENDER_NAME,
"email": BREVO_SENDER_EMAIL
},
"to": [
{
"email": to_email,
"name": to_email
}
],
"subject": subject,
"textContent": body
}
try:
response = requests.post(
BREVO_API_URL,
json=payload,
headers=headers,
timeout=30
)
print(f"Brevo Status Code: {response.status_code}")
print(f"Brevo Response: {response.text}")
if response.status_code in (200, 201):
print(f"Email sent successfully to: {to_email}")
return True
else:
print(f"Brevo FAILED: {response.status_code} - {response.text}")
return False
except requests.exceptions.Timeout:
print("Brevo Error: Request timed out")
return False
except requests.exceptions.ConnectionError:
print("Brevo Error: Cannot connect to Brevo API")
return False
except Exception as e:
print(f"Brevo Error: {e}")
return False
# =====================================================
# OTP EMAIL
# =====================================================
def generate_otp() -> str:
return str(random.randint(100000, 999999))
def send_otp_email(email: str, otp: str) -> bool:
subject = "College Leave Management System — Login OTP"
body = f"Your OTP is {otp}. Valid for 5 minutes."
return send_email_brevo(email, subject, body)
# =====================================================
# PARENT NOTIFICATION EMAIL
# =====================================================
def send_parent_email(
parent_email: str,
student_name: str,
from_date: str,
to_date: str,
reason: str,
status: str
) -> bool:
subject = f"Leave {status} Notification"
body = (
f"Dear Parent,\n\n"
f"Your ward {student_name} applied for leave.\n\n"
f"From: {from_date}\n"
f"To: {to_date}\n"
f"Reason: {reason}\n\n"
f"Status: {status}\n\n"
f"Thank you.\n"
f"College ERP System"
)
return send_email_brevo(parent_email, subject, body)
# =====================================================
# PYDANTIC MODELS
# =====================================================
class StudentRegister(BaseModel):
fullname: str
roll_no: int
registration_no: str
semester: int
student_email: str
parent_email: str
class SendOTP(BaseModel):
email: str
class OTPVerify(BaseModel):
email: str
otp: str
class LeaveApply(BaseModel):
student_id: int
from_date: str
to_date: str
reason: str
class EmergencyLeave(BaseModel):
student_id: int
from_date: str
to_date: str
reason: str
class Action(BaseModel):
leave_id: int
action: str
# =====================================================
# TOKEN VALIDATION
# =====================================================
def get_user_from_token(authorization: str) -> int:
if not authorization:
raise HTTPException(
status_code=401,
detail="Authorization header missing"
)
token = authorization.replace("Bearer ", "")
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"SELECT UserId FROM LoginSessions "
"WHERE Token=%s AND IsActive=1",
(token,)
)
row = cursor.fetchone()
if not row:
raise HTTPException(
status_code=401,
detail="Invalid session"
)
return row[0]
finally:
conn.close()
# =====================================================
# TEST BREVO EMAIL ENDPOINT
# =====================================================
@app.get("/test/email/{to_email}")
def test_email(to_email: str):
"""
Test endpoint to check if Brevo email works.
Usage: GET /test/email/someone@gmail.com
"""
success = send_email_brevo(
to_email=to_email,
subject="Test Email from College ERP",
body="If you received this, Brevo is working correctly!"
)
if success:
return {
"status": "success",
"message": f"Test email sent to {to_email}"
}
else:
raise HTTPException(
status_code=500,
detail="Brevo email failed. Check console logs."
)
# =====================================================
# DASHBOARD
# =====================================================
@app.get("/dashboard")
def dashboard():
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"SELECT COUNT(*) FROM Users "
"WHERE Role='STUDENT' AND IsActive=1"
)
count = cursor.fetchone()[0]
return {"total_students": count}
finally:
conn.close()
# =====================================================
# STUDENT REGISTER
# =====================================================
@app.post("/student/register")
def student_register(data: StudentRegister):
nms = 0
conn = get_connection()
try:
cursor = conn.cursor()
# Check duplicate email
cursor.execute(
"SELECT 1 FROM Users WHERE Email=%s",
(data.student_email,)
)
if cursor.fetchone():
raise HTTPException(
status_code=400,
detail="Email already registered"
)
# Check duplicate registration
cursor.execute(
"SELECT 1 FROM StudentProfile WHERE RegistrationNo=%s",
(data.registration_no,)
)
if cursor.fetchone():
raise HTTPException(
status_code=400,
detail="Student already registered"
)
# Insert user
cursor.execute(
"INSERT INTO Users (FullName, Email, Role) "
"VALUES (%s, %s, 'STUDENT')",
(data.fullname, data.student_email)
)
# Get new user ID
cursor.execute("SELECT SCOPE_IDENTITY()")
student_id = cursor.fetchone()[0]
nms = nms + 1
# Insert student profile
cursor.execute(
"""
INSERT INTO StudentProfile
(StudentId, RollNo, RegistrationNo,
Semester, StudentEmail, ParentEmail)
VALUES (%s, %s, %s, %s, %s, %s)
""",
(
student_id,
data.roll_no,
data.registration_no,
data.semester,
data.student_email,
data.parent_email
)
)
# Find available professor
cursor.execute(
"""
SELECT p.ProfessorId
FROM ProfessorProfile p
LEFT JOIN StudentProfessorMapping sp
ON p.ProfessorId = sp.ProfessorId
AND sp.Semester = %s
GROUP BY p.ProfessorId
HAVING COUNT(sp.StudentId) < 7
ORDER BY COUNT(sp.StudentId) ASC
""",
(data.semester,)
)
prof = cursor.fetchone()
if not prof:
raise HTTPException(
status_code=400,
detail="No professor available"
)
# Map student to professor
cursor.execute(
"""
INSERT INTO StudentProfessorMapping
(StudentId, ProfessorId, Semester)
VALUES (%s, %s, %s)
""",
(student_id, prof[0], data.semester)
)
conn.commit()
return {
"message": "Student registered",
"professor": prof[0],
"number_of_student": nms
}
except HTTPException:
raise
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
# =====================================================
# AUTH — SEND OTP (via Brevo)
# =====================================================
@app.post("/auth/send-otp")
def send_otp(data: SendOTP):
conn = get_connection()
try:
cursor = conn.cursor()
# Check user exists
cursor.execute(
"SELECT UserId FROM Users "
"WHERE Email=%s AND IsActive=1",
(data.email,)
)
user = cursor.fetchone()
if not user:
raise HTTPException(
status_code=404,
detail="User not found"
)
# Generate OTP
otp = generate_otp()
expiry = datetime.now() + timedelta(minutes=5)
# Save OTP to database
cursor.execute(
"INSERT INTO EmailOTP (Email, OTPCode, ExpiryTime) "
"VALUES (%s, %s, %s)",
(data.email, otp, expiry)
)
conn.commit()
# Send OTP via Brevo
email_sent = send_otp_email(data.email, otp)
if email_sent:
return {"message": "OTP sent successfully via Brevo"}
else:
return {
"message": "OTP saved in database but email failed",
"otp_for_debug": otp,
"warning": "Check Brevo API key and sender verification"
}
except HTTPException:
raise
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
# =====================================================
# AUTH — VERIFY OTP
# =====================================================
@app.post("/auth/verify-otp")
def verify_otp(data: OTPVerify):
conn = get_connection()
try:
cursor = conn.cursor()
# Validate OTP
cursor.execute(
"""
SELECT OTPId FROM EmailOTP
WHERE Email=%s AND OTPCode=%s
AND IsUsed=0 AND ExpiryTime>=GETDATE()
""",
(data.email, data.otp)
)
otp_row = cursor.fetchone()
if not otp_row:
raise HTTPException(
status_code=400,
detail="Invalid OTP"
)
# Mark OTP used
cursor.execute(
"UPDATE EmailOTP SET IsUsed=1 WHERE OTPId=%s",
(otp_row[0],)
)
# Get user
cursor.execute(
"SELECT UserId, Role FROM Users "
"WHERE Email=%s AND IsActive=1",
(data.email,)
)
user = cursor.fetchone()
if not user:
raise HTTPException(
status_code=404,
detail="User not found"
)
# Create session
token = str(uuid.uuid4())
cursor.execute(
"UPDATE LoginSessions SET IsActive=0 "
"WHERE UserId=%s",
(user[0],)
)
cursor.execute(
"INSERT INTO LoginSessions (UserId, Token) "
"VALUES (%s, %s)",
(user[0], token)
)
conn.commit()
return {
"login": "success",
"token": token,
"user_id": user[0],
"role": user[1]
}
except HTTPException:
raise
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
# =====================================================
# APPLY LEAVE
# =====================================================
@app.post("/leave/apply")
def apply_leave(
data: LeaveApply,
authorization: str = Header(None)
):
data.student_id = get_user_from_token(authorization)
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"SELECT ProfessorId FROM StudentProfessorMapping WHERE StudentId=%s",
(data.student_id,)
)
prof = cursor.fetchone()
cursor.execute("SELECT DeanId FROM DeanProfile")
dean = cursor.fetchone()
if not prof or not dean:
raise HTTPException(status_code=400, detail="Config error")
# INSERT
cursor.execute(
"""
INSERT INTO LeaveApplications
(StudentId, ProfessorId, DeanId,
ProfessorStatus, DeanStatus,
FromDate, ToDate, Reason)
VALUES (%s,%s,%s,'PENDING','PENDING',%s,%s,%s)
""",
(
data.student_id,
prof[0],
dean[0],
data.from_date,
data.to_date,
data.reason
)
)
# Inside @app.post("/leave/apply")
cursor.execute(
"SELECT p.FcmToken FROM Users p "
"JOIN StudentProfessorMapping sp ON p.UserId = sp.ProfessorId "
"WHERE sp.StudentId = %s",
(data.student_id,)
)
prof_token = cursor.fetchone()
if prof_token and prof_token[0]:
send_fcm(prof_token[0], "New Leave Request", f"Student {data.student_id} has applied for leave.")
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()# =====================================================
# STUDENT LEAVE VIEWS
# =====================================================
@app.get("/student/rejected/{student_id}")
def student_rejected(student_id: int):
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT LeaveId, FromDate, ToDate, Reason
FROM LeaveApplications
WHERE StudentId=%s
AND FinalStatus LIKE 'REJECTED%%'
""",
(student_id,)
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/student/approved/{student_id}")
def student_approved(student_id: int):
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT LeaveId, FromDate, ToDate, Reason
FROM LeaveApplications
WHERE StudentId=%s
AND FinalStatus='FINAL_APPROVED'
""",
(student_id,)
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/student/leaves/{student_id}") # ✅ Added {student_id} to match Flutter
def student_leaves(student_id: int): # ✅ Accepting ID as a parameter
conn = get_connection()
try:
cursor = conn.cursor()
# Fetching TOP 5 to keep the dashboard clean
cursor.execute(
"""
SELECT TOP 5
LeaveId, ProfessorStatus, DeanStatus, FinalStatus, FromDate, ToDate, Reason
FROM LeaveApplications
WHERE StudentId=%s
ORDER BY LeaveId DESC
""",
(student_id,)
)
rows = cursor.fetchall()
results = []
for r in rows:
# --- STATUS MINING LOGIC ---
# This tells the student exactly who needs to take action next
if r[3] == 'PENDING' and r[1] == 'PENDING':
mining_status = "Waiting for Professor"
elif r[1] == 'APPROVED' and r[2] == 'PENDING':
mining_status = "Waiting for Dean"
elif r[3] == 'FINAL_APPROVED':
mining_status = "Approved ✅"
elif 'REJECTED' in r[3]:
mining_status = "Rejected ❌"
else:
mining_status = r[3] # Fallback to FinalStatus
results.append({
"leave_id": r[0],
"professor_status": r[1],
"dean_status": r[2],
"final_status": r[3],
"display_status": mining_status, # ✅ NEW: Send this to Flutter
"from_date": str(r[4]),
"to_date": str(r[5]),
"reason": r[6]
})
return results
finally:
conn.close()
# =====================================================
# EMERGENCY LEAVE
# =====================================================
@app.post("/leave/emergency")
def emergency_leave(
data: EmergencyLeave,
authorization: str = Header(None)
):
data.student_id= get_user_from_token(authorization)
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"SELECT ProfessorId FROM StudentProfessorMapping "
"WHERE StudentId=%s",
(data.student_id,)
)
prof = cursor.fetchone()
cursor.execute("SELECT DeanId FROM DeanProfile")
dean = cursor.fetchone()
if not prof or not dean:
raise HTTPException(
status_code=400,
detail="Configuration error"
)
cursor.execute(
"""
INSERT INTO LeaveApplications
(StudentId, ProfessorId, DeanId,
ProfessorStatus, DeanStatus,
FromDate, ToDate, Reason)
VALUES (%s, %s, %s, 'SKIPPED', 'PENDING', %s, %s, %s)
""",
(
data.student_id,
prof[0],
dean[0],
data.from_date,
data.to_date,
data.reason
)
)
# Inside @app.post("/leave/emergency")
cursor.execute("SELECT FcmToken FROM Users WHERE Role='DEAN'")
dean_token = cursor.fetchone()
if dean_token and dean_token[0]:
send_fcm(dean_token[0], "🚨 EMERGENCY LEAVE", f"Student {data.student_id} requested emergency leave.")
except HTTPException:
raise
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
# =====================================================
# DEAN APIs
# =====================================================
@app.get("/dean/pending")
def dean_pending():
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT l.LeaveId, l.StudentId, u.FullName,
s.Semester, l.FromDate, l.ToDate, l.Reason
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.DeanStatus='PENDING'
"""
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/dean/approved")
def dean_approved():
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT l.LeaveId, l.StudentId, u.FullName,
s.Semester, l.FromDate, l.ToDate, l.Reason
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.DeanStatus='APPROVED'
"""
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/dean/emergency")
def dean_emergency():
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT l.LeaveId, l.StudentId, u.FullName,
s.Semester, l.FromDate, l.ToDate, l.Reason
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.ProfessorStatus='SKIPPED'
AND l.DeanStatus='PENDING'
"""
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/dean/students")
def dean_students():
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT u.UserId, u.FullName, s.RollNo,
s.RegistrationNo, s.Semester,
s.StudentEmail, s.ParentEmail
FROM Users u
JOIN StudentProfile s ON u.UserId = s.StudentId
WHERE u.Role='STUDENT' AND u.IsActive=1
"""
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.post("/leave/dean-action")
def dean_action(data: Action):
conn = get_connection()
try:
cursor = conn.cursor()
status = "APPROVED" if data.action == "APPROVED" else "REJECTED"
final_status = (
"FINAL_APPROVED" if status == "APPROVED"
else "REJECTED_BY_DEAN"
)
cursor.execute(
"""
UPDATE LeaveApplications
SET DeanStatus=%s, FinalStatus=%s
WHERE LeaveId=%s
""",
(status, final_status, data.leave_id)
)
# Get student + parent info
cursor.execute(
"""
SELECT u.FullName, s.ParentEmail,
l.FromDate, l.ToDate, l.Reason
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.LeaveId=%s
""",
(data.leave_id,)
)
row = cursor.fetchone()
# Send parent email via Brevo
email_sent = False
if row:
try:
email_sent = send_parent_email(
parent_email=row[1],
student_name=row[0],
from_date=str(row[2]),
to_date=str(row[3]),
reason=row[4],
status=status
)
except Exception as mail_err:
print(f"Parent Email Error: {mail_err}")
# ✅ GET STUDENT TOKEN
cursor.execute(
"SELECT u.FcmToken FROM LeaveApplications l "
"JOIN Users u ON l.StudentId=u.UserId "
"WHERE l.LeaveId=%s",
(data.leave_id,)
)
student = cursor.fetchone()
conn.commit()
# ✅ SEND FCM
if student and student[0]:
send_fcm(
student[0],
"Leave Status",
f"Your leave is {status}"
)
# Inside @app.post("/leave/dean-action")
cursor.execute(
"SELECT FcmToken FROM Users WHERE UserId = "
"(SELECT StudentId FROM LeaveApplications WHERE LeaveId = %s)",
(data.leave_id,)
)
student_token = cursor.fetchone()
if student_token and student_token[0]:
status_msg = "Approved" if data.action == "APPROVED" else "Rejected"
send_fcm(student_token[0], "Leave Decision", f"The Dean has {status_msg} your leave request.")
return {
"message": f"Dean action done ({status})",
"parent_email_sent": email_sent
}
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
@app.get("/dean/semester-wise")
def semester_wise():
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT s.Semester,
u.FullName AS StudentName,
s.RollNo,
pUser.FullName AS ProfessorName
FROM StudentProfessorMapping sp
JOIN Users u ON sp.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = u.UserId
JOIN Users pUser ON pUser.UserId = sp.ProfessorId
ORDER BY s.Semester, s.RollNo
"""
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
# =====================================================
# PROFESSOR APIs
# =====================================================
@app.get("/professor/pending/{professor_id}")
def professor_pending(professor_id: int):
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT l.LeaveId, l.StudentId, u.FullName,
s.Semester, l.FromDate, l.ToDate, l.Reason
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.ProfessorId=%s
AND l.ProfessorStatus='PENDING'
""",
(professor_id,)
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/professor/approved/{professor_id}")
def professor_approved(professor_id: int):
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT l.LeaveId, u.FullName, s.Semester,
l.FromDate, l.ToDate
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.ProfessorId=%s
AND l.ProfessorStatus='APPROVED'
""",
(professor_id,)
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/professor/rejected/{professor_id}")
def professor_rejected(professor_id: int):
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT l.LeaveId, u.FullName, s.Semester,
l.FromDate, l.ToDate
FROM LeaveApplications l
JOIN Users u ON l.StudentId = u.UserId
JOIN StudentProfile s ON s.StudentId = l.StudentId
WHERE l.ProfessorId=%s
AND l.ProfessorStatus='REJECTED'
""",
(professor_id,)
)
rows = cursor.fetchall()
return [list(r) for r in rows]
finally:
conn.close()
@app.get("/professor/students/{professor_id}")
def professor_students(professor_id: int):
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"""
SELECT sp.Semester,
COUNT(*) AS total_students
FROM StudentProfessorMapping sp
WHERE sp.ProfessorId=%s
GROUP BY sp.Semester
""",
(professor_id,)
)
rows = cursor.fetchall()
return [
{
"semester": r[0],
"total_students": r[1]
}
for r in rows
]
finally:
conn.close()
@app.post("/leave/professor-action")
def professor_action(data: Action):
conn = get_connection()
try:
cursor = conn.cursor()
status = "APPROVED" if data.action == "APPROVED" else "REJECTED"
final_status = (
"FORWARDED_TO_DEAN" if status == "APPROVED"
else "REJECTED_BY_PROFESSOR"
)
cursor.execute(
"""
UPDATE LeaveApplications
SET ProfessorStatus=%s, FinalStatus=%s
WHERE LeaveId=%s
""",
(status, final_status, data.leave_id)
)
# Inside @app.post("/leave/professor-action")
if data.action == "APPROVED":
# Get the Dean's token (assuming there is one Dean or a specific DeanId)
cursor.execute("SELECT FcmToken FROM Users WHERE Role='DEAN'")
dean_token = cursor.fetchone()
if dean_token and dean_token[0]:
send_fcm(dean_token[0], "Leave Forwarded", f"Professor approved Leave ID {data.leave_id}. Action required.")
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
# =====================================================
# USER PROFILE
# =====================================================
@app.get("/user/profile")
def get_profile(authorization: str = Header(None)):
user_id = get_user_from_token(authorization)
conn = get_connection()
try:
cursor = conn.cursor()
cursor.execute(
"SELECT FullName, Email, Role "
"FROM Users WHERE UserId=%s",
(user_id,)
)
user = cursor.fetchone()
if not user:
raise HTTPException(
status_code=404,
detail="User not found"
)
role = user[2]
profile_data = {
"user_id": user_id,
"name": user[0],
"email": user[1],
"role": role
}
# STUDENT
if role == "STUDENT":
cursor.execute(
"""
SELECT RollNo, RegistrationNo, Semester,
StudentEmail, ParentEmail
FROM StudentProfile
WHERE StudentId=%s
""",
(user_id,)
)
data = cursor.fetchone()
if data:
profile_data.update({
"roll_no": data[0],
"registration_no": data[1],
"semester": data[2],
"student_email": data[3],
"parent_email": data[4],
})
# PROFESSOR
elif role == "PROFESSOR":
cursor.execute(
"""
SELECT ProfessorCode, Email
FROM ProfessorProfile
WHERE ProfessorId=%s
""",
(user_id,)
)
data = cursor.fetchone()
if data:
profile_data.update({
"professor_code": data[0],
"email": data[1],
})
# DEAN
elif role == "DEAN":
cursor.execute(
"""
SELECT DeanCode, Email
FROM DeanProfile
WHERE DeanId=%s
""",
(user_id,)
)
data = cursor.fetchone()
if data:
profile_data.update({
"dean_code": data[0],
"email": data[1],
})
return profile_data
finally:
conn.close()
#==========================================================
# ===============================
# ADD PROFESSOR (DEAN)
# ==========================================================
# ADD PROFESSOR (DEAN)
# ==========================================================
class ProfessorCreate(BaseModel):
full_name: str
email: str
@app.post("/dean/add-professor")
def add_professor(prof: ProfessorCreate):
conn = get_connection()
try:
cursor = conn.cursor()
# check email already exists
cursor.execute(
"SELECT UserId FROM Users WHERE Email=%s",
(prof.email,)
)
if cursor.fetchone():
raise HTTPException(
status_code=400,
detail="Email already exists"
)
# insert into Users
cursor.execute(
"""
INSERT INTO Users (FullName, Email, Role, IsActive, CreatedAt)
VALUES (%s,%s,'PROFESSOR',1,GETDATE())
""",
(prof.full_name, prof.email)
)
# get new professor id
cursor.execute("SELECT SCOPE_IDENTITY()")
professor_id = cursor.fetchone()[0]
# generate professor code
professor_code = f"PROF{int(professor_id):04d}"
# insert into ProfessorProfile
cursor.execute(
"""
INSERT INTO ProfessorProfile
(ProfessorId, ProfessorCode, Email)
VALUES (%s,%s,%s)
""",
(
professor_id,
professor_code,
prof.email
)
)
conn.commit()
return {
"message": "Professor added successfully",
"professor_id": professor_id,
"professor_code": professor_code
}
except Exception as e:
conn.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
conn.close()
#==========================================================
#============================fcm tocken==============================
class SaveToken(BaseModel):
user_id: int
fcm_token: str
@app.post("/save-fcm-token")
def save_fcm_token(
data: SaveToken,
authorization: str = Header(None) # FastAPI looks for 'Authorization' header
):
# This line triggers the 401 if 'authorization' is missing or token is invalid
user_id_from_db = get_user_from_token(authorization)
conn = get_connection()
try:
cursor = conn.cursor()
# Use the ID from the token for better security
cursor.execute(
"UPDATE Users SET FcmToken=%s WHERE UserId=%s",
(data.fcm_token, user_id_from_db)
)
conn.commit()
return {"message": "Token saved successfully"}
except Exception as e:
print(f"Error: {e}")
raise HTTPException(status_code=500, detail="Database error")
finally:
conn.close()
#=======================================================
def send_fcm(token, title, body):
try:
message = messaging.Message(
notification=messaging.Notification(
title=title,
body=body,
),
token=token,
)
response = messaging.send(message)
print(f"✅ FCM sent successfully: {response}")
except Exception as e:
# This is where your 'Permission denied' error is coming from
print(f"❌ FCM FATAL ERROR: {str(e)}")
# =====================================================
# RUN
# =====================================================
if __name__ == "__main__":
import uvicorn
uvicorn.run("app:app", host="0.0.0.0", port=7860)