AmaanP314 commited on
Commit
5e37a6e
·
1 Parent(s): 7b52ec4

initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.pyo
4
+ *.pyd
5
+ *.db
6
+ *.sqlite3
7
+ *.log
8
+ *.env
9
+ *.venv
10
+ *.egg-info/
11
+ dist/
12
+ build/
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12.7
2
+ WORKDIR /code
3
+ COPY ./requirements.txt /code/requirements.txt
4
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
5
+
6
+ RUN useradd user
7
+ USER user
8
+
9
+ ENV HOME=/home/user \
10
+ PATH=/home/user/.local/bin:$PATH
11
+
12
+ WORKDIR $HOME/app
13
+
14
+ COPY --chown=user . $HOME/app
15
+
16
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
app/__init__.py ADDED
File without changes
app/auth.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta, timezone
2
+ from typing import Optional
3
+ from fastapi import Depends, HTTPException, status
4
+ from fastapi.security import OAuth2PasswordBearer
5
+ from jose import JWTError, jwt
6
+ from passlib.context import CryptContext
7
+ from sqlalchemy.orm import Session
8
+
9
+ from . import crud, models, schemas
10
+ from .database import get_db
11
+ from .settings import settings
12
+
13
+ # Password Hashing
14
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
15
+
16
+ # OAuth2 Scheme
17
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
18
+
19
+ def verify_password(plain_password, hashed_password):
20
+ return pwd_context.verify(plain_password, hashed_password)
21
+
22
+ def get_password_hash(password):
23
+ return pwd_context.hash(password)
24
+
25
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
26
+ to_encode = data.copy()
27
+ if expires_delta:
28
+ expire = datetime.now(timezone.utc) + expires_delta
29
+ else:
30
+ expire = datetime.now(timezone.utc) + timedelta(minutes=15)
31
+ to_encode.update({"exp": expire})
32
+ encoded_jwt = jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
33
+ return encoded_jwt
34
+
35
+ def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
36
+ credentials_exception = HTTPException(
37
+ status_code=status.HTTP_401_UNAUTHORIZED,
38
+ detail="Could not validate credentials",
39
+ headers={"WWW-Authenticate": "Bearer"},
40
+ )
41
+ try:
42
+ payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm])
43
+ username: str = payload.get("sub")
44
+ if username is None:
45
+ raise credentials_exception
46
+ token_data = schemas.TokenData(username=username)
47
+ except JWTError:
48
+ raise credentials_exception
49
+ user = crud.get_user_by_username(db, username=token_data.username)
50
+ if user is None:
51
+ raise credentials_exception
52
+ return user
53
+
54
+ def get_current_active_teacher(current_user: models.User = Depends(get_current_user)):
55
+ if current_user.role != models.UserRole.teacher:
56
+ raise HTTPException(status_code=403, detail="Not authorized")
57
+ return current_user
app/crud.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from . import models, schemas, auth
3
+
4
+ def get_user_by_username(db: Session, username: str):
5
+ return db.query(models.User).filter(models.User.username == username).first()
6
+
7
+ def create_user(db: Session, user: schemas.UserCreate):
8
+ hashed_password = auth.get_password_hash(user.password)
9
+ db_user = models.User(
10
+ username=user.username,
11
+ hashed_password=hashed_password,
12
+ full_name=user.full_name,
13
+ role=user.role
14
+ )
15
+ db.add(db_user)
16
+ db.commit()
17
+ db.refresh(db_user)
18
+ return db_user
19
+
20
+ def get_lectures_by_day(db: Session, day_of_week: int, teacher_id: int):
21
+ return db.query(models.TimetableSlot).filter(
22
+ models.TimetableSlot.day_of_week == day_of_week,
23
+ models.TimetableSlot.teacher_id == teacher_id
24
+ ).all()
25
+
26
+ def check_if_already_marked(db: Session, student_id: int, session_id: str):
27
+ return db.query(models.AttendanceRecord).filter(
28
+ models.AttendanceRecord.student_id == student_id,
29
+ models.AttendanceRecord.session_id == session_id
30
+ ).first() is not None
31
+
32
+ def create_attendance_record(db: Session, student_id: int, slot_id: int, session_id: str):
33
+ db_record = models.AttendanceRecord(
34
+ student_id=student_id,
35
+ slot_id=slot_id,
36
+ session_id=session_id
37
+ )
38
+ db.add(db_record)
39
+ db.commit()
40
+ db.refresh(db_record)
41
+ return db_record
app/database.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import sessionmaker
4
+ import redis
5
+ from .settings import settings
6
+
7
+ # PostgreSQL Setup
8
+ engine = create_engine(settings.database_url)
9
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
10
+ Base = declarative_base()
11
+
12
+ # Redis Setup
13
+ redis_client = redis.from_url(settings.redis_url, decode_responses=True)
14
+
15
+
16
+ # Dependency to get a DB session
17
+ def get_db():
18
+ db = SessionLocal()
19
+ try:
20
+ yield db
21
+ finally:
22
+ db.close()
23
+
24
+ # Dependency to get a Redis client
25
+ def get_redis():
26
+ return redis_client
app/models.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, ForeignKey, Time, DateTime, Enum as SQLAlchemyEnum
2
+ from sqlalchemy.orm import relationship
3
+ from sqlalchemy.sql import func
4
+ import enum
5
+ from .database import Base
6
+
7
+ class UserRole(enum.Enum):
8
+ teacher = "teacher"
9
+ student = "student"
10
+
11
+ class User(Base):
12
+ __tablename__ = "users"
13
+ id = Column(Integer, primary_key=True, index=True)
14
+ username = Column(String, unique=True, index=True, nullable=False)
15
+ hashed_password = Column(String, nullable=False)
16
+ full_name = Column(String, nullable=False)
17
+ role = Column(SQLAlchemyEnum(UserRole), nullable=False)
18
+
19
+ class Subject(Base):
20
+ __tablename__ = "subjects"
21
+ id = Column(Integer, primary_key=True, index=True)
22
+ name = Column(String, nullable=False)
23
+ code = Column(String, unique=True, nullable=False)
24
+
25
+ class TimetableSlot(Base):
26
+ __tablename__ = "timetable_slots"
27
+ id = Column(Integer, primary_key=True, index=True)
28
+ subject_id = Column(Integer, ForeignKey("subjects.id"), nullable=False)
29
+ teacher_id = Column(Integer, ForeignKey("users.id"), nullable=False)
30
+ day_of_week = Column(Integer, nullable=False) # 0=Monday, 1=Tuesday...
31
+ start_time = Column(Time, nullable=False)
32
+ end_time = Column(Time, nullable=False)
33
+
34
+ subject = relationship("Subject")
35
+ teacher = relationship("User")
36
+
37
+ class AttendanceRecord(Base):
38
+ __tablename__ = "attendance_records"
39
+ id = Column(Integer, primary_key=True, index=True)
40
+ student_id = Column(Integer, ForeignKey("users.id"), nullable=False)
41
+ slot_id = Column(Integer, ForeignKey("timetable_slots.id"), nullable=False)
42
+ session_id = Column(String, nullable=False, index=True)
43
+ timestamp = Column(DateTime(timezone=True), server_default=func.now())
44
+
45
+ student = relationship("User")
46
+ slot = relationship("TimetableSlot")
app/routers/__init__.py ADDED
File without changes
app/routers/auth.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import timedelta
2
+ from fastapi import APIRouter, Depends, HTTPException, status
3
+ from fastapi.security import OAuth2PasswordRequestForm
4
+ from sqlalchemy.orm import Session
5
+
6
+ from .. import auth, crud, schemas, models
7
+ from ..database import get_db
8
+ from ..settings import settings
9
+
10
+ router = APIRouter(
11
+ prefix="/auth",
12
+ tags=["Authentication"],
13
+ )
14
+
15
+ @router.post("/token", response_model=schemas.Token)
16
+ def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
17
+ user = crud.get_user_by_username(db, username=form_data.username)
18
+ if not user or not auth.verify_password(form_data.password, user.hashed_password):
19
+ raise HTTPException(
20
+ status_code=status.HTTP_401_UNAUTHORIZED,
21
+ detail="Incorrect username or password",
22
+ headers={"WWW-Authenticate": "Bearer"},
23
+ )
24
+ access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
25
+ access_token = auth.create_access_token(
26
+ data={"sub": user.username}, expires_delta=access_token_expires
27
+ )
28
+ return {"access_token": access_token, "token_type": "bearer"}
29
+
30
+ @router.get("/me", response_model=schemas.User)
31
+ def read_users_me(current_user: models.User = Depends(auth.get_current_user)):
32
+ """
33
+ Get the current logged-in user's details.
34
+
35
+ This endpoint is protected. It uses the `get_current_user` dependency
36
+ to validate the JWT token from the Authorization header and return
37
+ the corresponding user's data from the database.
38
+ """
39
+ return current_user
app/routers/student.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status
2
+ from redis import Redis
3
+ from sqlalchemy.orm import Session
4
+ import json
5
+
6
+ from .. import auth, crud, models, schemas
7
+ from ..database import get_db, get_redis
8
+ from .teacher import manager # Import the connection manager
9
+
10
+ router = APIRouter(
11
+ prefix="/student",
12
+ tags=["Student"],
13
+ dependencies=[Depends(auth.get_current_user)]
14
+ )
15
+
16
+ @router.post("/attendance/scan")
17
+ async def scan_attendance(
18
+ payload: schemas.AttendanceScanPayload,
19
+ db: Session = Depends(get_db),
20
+ redis_client: Redis = Depends(get_redis),
21
+ current_user: models.User = Depends(auth.get_current_user)
22
+ ):
23
+ if current_user.role != models.UserRole.student:
24
+ raise HTTPException(status_code=403, detail="Only students can scan for attendance")
25
+
26
+ # 1. Verify Token
27
+ valid_token = redis_client.get(f"qr_token:{payload.session_id}")
28
+ if not valid_token or valid_token != payload.token:
29
+ raise HTTPException(status_code=400, detail="Invalid or expired QR code")
30
+
31
+ # 2. Get lecture ID for this session
32
+ slot_id = redis_client.get(f"session_slot:{payload.session_id}")
33
+ if not slot_id:
34
+ raise HTTPException(status_code=404, detail="Attendance session not found")
35
+
36
+ # 3. Check for duplicates
37
+ if crud.check_if_already_marked(db, student_id=current_user.id, session_id=payload.session_id):
38
+ raise HTTPException(status_code=409, detail="Attendance already marked for this session")
39
+
40
+ # 4. Mark attendance
41
+ record = crud.create_attendance_record(
42
+ db,
43
+ student_id=current_user.id,
44
+ slot_id=int(slot_id),
45
+ session_id=payload.session_id
46
+ )
47
+
48
+ # 5. Notify teacher via WebSocket
49
+ student_data = {
50
+ "id": current_user.id,
51
+ "name": current_user.full_name,
52
+ "email": current_user.username
53
+ }
54
+ await manager.broadcast_to_session(json.dumps(student_data), payload.session_id)
55
+
56
+ return {"status": "success", "message": "Attendance marked successfully"}
app/routers/teacher.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import uuid
2
+ # import asyncio
3
+ # from datetime import date
4
+ # from typing import List, Dict
5
+
6
+ # from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, HTTPException
7
+ # from redis import Redis
8
+ # from sqlalchemy.orm import Session
9
+
10
+ # from .. import auth, crud, models, schemas
11
+ # from ..database import get_db, get_redis
12
+
13
+ # router = APIRouter(
14
+ # prefix="/teacher",
15
+ # tags=["Teacher"],
16
+ # dependencies=[Depends(auth.get_current_active_teacher)]
17
+ # )
18
+
19
+ # class ConnectionManager:
20
+ # def __init__(self):
21
+ # self.active_connections: Dict[str, WebSocket] = {}
22
+
23
+ # async def connect(self, websocket: WebSocket, session_id: str):
24
+ # await websocket.accept()
25
+ # self.active_connections[session_id] = websocket
26
+
27
+ # def disconnect(self, session_id: str):
28
+ # if session_id in self.active_connections:
29
+ # del self.active_connections[session_id]
30
+
31
+ # async def send_personal_message(self, message: str, websocket: WebSocket):
32
+ # await websocket.send_text(message)
33
+
34
+ # async def broadcast_to_session(self, message: str, session_id: str):
35
+ # if session_id in self.active_connections:
36
+ # await self.active_connections[session_id].send_text(message)
37
+
38
+ # manager = ConnectionManager()
39
+
40
+ # @router.get("/lectures", response_model=List[schemas.TimetableSlot])
41
+ # def get_teacher_lectures_for_date(
42
+ # selected_date: date,
43
+ # db: Session = Depends(get_db),
44
+ # current_user: models.User = Depends(auth.get_current_active_teacher)
45
+ # ):
46
+ # day_of_week = selected_date.weekday()
47
+ # return crud.get_lectures_by_day(db, day_of_week=day_of_week, teacher_id=current_user.id)
48
+
49
+ # @router.post("/attendance/start")
50
+ # def start_attendance_session(
51
+ # slot_id: int,
52
+ # redis_client: Redis = Depends(get_redis)
53
+ # ):
54
+ # session_id = str(uuid.uuid4())
55
+ # token = str(uuid.uuid4())
56
+
57
+ # # Store token and associated lecture (slot_id) in Redis
58
+ # redis_client.set(f"qr_token:{session_id}", token, ex=20) # ex=20 -> expires in 20 seconds
59
+ # redis_client.set(f"session_slot:{session_id}", slot_id)
60
+
61
+ # return {"session_id": session_id, "initial_token": token}
62
+
63
+ # @router.get("/attendance/qr/{session_id}")
64
+ # def refresh_qr_token(session_id: str, redis_client: Redis = Depends(get_redis)):
65
+ # if not redis_client.exists(f"session_slot:{session_id}"):
66
+ # raise HTTPException(status_code=404, detail="Attendance session not found or expired")
67
+
68
+ # new_token = str(uuid.uuid4())
69
+ # redis_client.set(f"qr_token:{session_id}", new_token, ex=20)
70
+ # return {"token": new_token}
71
+
72
+ # @router.websocket("/ws/attendance/{session_id}")
73
+ # async def websocket_endpoint(websocket: WebSocket, session_id: str):
74
+ # await manager.connect(websocket, session_id)
75
+ # try:
76
+ # while True:
77
+ # # Keep connection alive
78
+ # await asyncio.sleep(1)
79
+ # except WebSocketDisconnect:
80
+ # manager.disconnect(session_id)
81
+
82
+ import uuid
83
+ import asyncio
84
+ from datetime import date
85
+ from typing import List, Dict
86
+
87
+ from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, HTTPException, Query, status
88
+ from redis import Redis
89
+ from sqlalchemy.orm import Session
90
+
91
+ from .. import auth, crud, models, schemas
92
+ from ..database import get_db, get_redis
93
+
94
+ # --- NEW: WebSocket-specific authenticator ---
95
+ # This function will be used ONLY by the websocket endpoint.
96
+ # It looks for the token in a query parameter instead of a header.
97
+ async def get_current_user_from_query(
98
+ token: str = Query(...),
99
+ db: Session = Depends(get_db)
100
+ ):
101
+ user = auth.get_current_user(token=token, db=db)
102
+ if user.role != models.UserRole.teacher:
103
+ raise WebSocketDisconnect(code=status.WS_1008_POLICY_VIOLATION, reason="Not authorized")
104
+ return user
105
+
106
+
107
+ # --- MODIFIED: Router definition ---
108
+ # We REMOVE the global dependency from the router itself.
109
+ router = APIRouter(
110
+ prefix="/teacher",
111
+ tags=["Teacher"]
112
+ )
113
+
114
+ class ConnectionManager:
115
+ def __init__(self):
116
+ self.active_connections: Dict[str, WebSocket] = {}
117
+
118
+ async def connect(self, websocket: WebSocket, session_id: str):
119
+ await websocket.accept()
120
+ self.active_connections[session_id] = websocket
121
+
122
+ def disconnect(self, session_id: str):
123
+ if session_id in self.active_connections:
124
+ del self.active_connections[session_id]
125
+
126
+ async def broadcast_to_session(self, message: str, session_id: str):
127
+ if session_id in self.active_connections:
128
+ await self.active_connections[session_id].send_text(message)
129
+
130
+ manager = ConnectionManager()
131
+
132
+ # --- MODIFIED: Add the dependency directly to the HTTP routes ---
133
+ @router.get("/lectures", response_model=List[schemas.TimetableSlot], dependencies=[Depends(auth.get_current_active_teacher)])
134
+ def get_teacher_lectures_for_date(
135
+ selected_date: date,
136
+ db: Session = Depends(get_db),
137
+ current_user: models.User = Depends(auth.get_current_active_teacher)
138
+ ):
139
+ day_of_week = selected_date.weekday()
140
+ return crud.get_lectures_by_day(db, day_of_week=day_of_week, teacher_id=current_user.id)
141
+
142
+ @router.post("/attendance/start", dependencies=[Depends(auth.get_current_active_teacher)])
143
+ def start_attendance_session(
144
+ slot_id: int,
145
+ redis_client: Redis = Depends(get_redis)
146
+ ):
147
+ session_id = str(uuid.uuid4())
148
+ token = str(uuid.uuid4())
149
+
150
+ redis_client.set(f"qr_token:{session_id}", token, ex=20)
151
+ redis_client.set(f"session_slot:{session_id}", slot_id)
152
+
153
+ return {"session_id": session_id, "initial_token": token}
154
+
155
+ @router.get("/attendance/qr/{session_id}", dependencies=[Depends(auth.get_current_active_teacher)])
156
+ def refresh_qr_token(session_id: str, redis_client: Redis = Depends(get_redis)):
157
+ if not redis_client.exists(f"session_slot:{session_id}"):
158
+ raise HTTPException(status_code=404, detail="Attendance session not found or expired")
159
+
160
+ new_token = str(uuid.uuid4())
161
+ redis_client.set(f"qr_token:{session_id}", new_token, ex=20)
162
+ print( f"Refreshed token for session {session_id}: {new_token}" )
163
+ return {"token": new_token}
164
+
165
+ # --- MODIFIED: WebSocket endpoint now uses its own authenticator ---
166
+ @router.websocket("/ws/attendance/{session_id}")
167
+ async def websocket_endpoint(
168
+ websocket: WebSocket,
169
+ session_id: str,
170
+ # This dependency will now be resolved from the query parameter
171
+ user: models.User = Depends(get_current_user_from_query)
172
+ ):
173
+ await manager.connect(websocket, session_id)
174
+ try:
175
+ while True:
176
+ # Keep connection alive
177
+ await asyncio.sleep(1)
178
+ except WebSocketDisconnect:
179
+ manager.disconnect(session_id)
180
+
app/schemas.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List
3
+ import datetime
4
+ from .models import UserRole
5
+
6
+ # --- User Schemas ---
7
+ class UserBase(BaseModel):
8
+ username: str
9
+ full_name: str
10
+
11
+ class UserCreate(UserBase):
12
+ password: str
13
+ role: UserRole
14
+
15
+ class User(UserBase):
16
+ id: int
17
+ role: UserRole
18
+
19
+ class Config:
20
+ from_attributes = True
21
+
22
+ # --- Auth Schemas ---
23
+ class Token(BaseModel):
24
+ access_token: str
25
+ token_type: str
26
+
27
+ class TokenData(BaseModel):
28
+ username: str | None = None
29
+
30
+ # --- Timetable Schemas ---
31
+ class Subject(BaseModel):
32
+ id: int
33
+ name: str
34
+ code: str
35
+
36
+ class Config:
37
+ from_attributes = True
38
+
39
+ class TimetableSlot(BaseModel):
40
+ id: int
41
+ day_of_week: int
42
+ start_time: datetime.time
43
+ end_time: datetime.time
44
+ subject: Subject
45
+
46
+ class Config:
47
+ from_attributes = True
48
+
49
+ # --- Attendance Schemas ---
50
+ class AttendanceScanPayload(BaseModel):
51
+ token: str
52
+ session_id: str
53
+
54
+ class LiveAttendanceUpdate(BaseModel):
55
+ student_name: str
56
+ timestamp: datetime.datetime
app/settings.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+
3
+ class Settings(BaseSettings):
4
+ """Loads environment variables from the .env file."""
5
+ database_url: str
6
+ redis_url: str
7
+ secret_key: str
8
+ algorithm: str
9
+ access_token_expire_minutes: int
10
+
11
+ class Config:
12
+ env_file = ".\.env"
13
+
14
+ settings = Settings()
main.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from app import models
4
+ from app.database import engine
5
+ from app.routers import auth, teacher, student
6
+
7
+ # Create all database tables
8
+ models.Base.metadata.create_all(bind=engine)
9
+
10
+ app = FastAPI()
11
+
12
+ # CORS Middleware Setup
13
+ # Replace "http://localhost:3000" with your Next.js frontend URL
14
+ origins = [
15
+ "http://localhost:3000",
16
+ "http://127.0.0.1:63442",
17
+ "http://localhost:9002",
18
+ ]
19
+
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=origins,
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ # Include Routers
29
+ app.include_router(auth.router)
30
+ app.include_router(teacher.router)
31
+ app.include_router(student.router)
32
+
33
+ @app.get("/")
34
+ def read_root():
35
+ return {"message": "Welcome to the Smart Attendance System API"}
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.111.0
2
+ uvicorn[standard]==0.29.0
3
+ sqlalchemy==2.0.30
4
+ psycopg2-binary==2.9.9
5
+ redis==5.0.4
6
+ pydantic==2.7.1
7
+ pydantic-settings==2.2.1
8
+ python-jose[cryptography]==3.3.0
9
+ passlib[bcrypt]
10
+ python-multipart==0.0.9
11
+ websockets==12.0